4
votes

How do I create and return a continuation Task using reflection or by any other means if I only have access the Task?

I need a way to let an exception in the continuation travel back to the original caller. As far as I know this can only be done by returning the continuation task instead of the original task. The problem comes in that I don't know the result type of the Task so can't create a proper continuation Task.

Edit: I cannot change signature types. I have many interfaces that return Task< TResult > objects and can't expect the client to get Task< Object > results. These interfaces are WCF contracts. I want to do some extra validation logic after the "core" logic method is done and throw an exception if needed. This exception must travel back to client but it does not travel back currently because I'm not returning the continuation task yet. Also I don't know the type beforehand because I'm applying a postsharp aspect and using the OnExit() override, this gives me access to a return value that I know is a Task but it can be any number of Task objects of which TResult is only known at runtime.

using System;
using System.Threading.Tasks;

    namespace TaskContinueWith
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                try
                {
                    Task<string> myTask = Interceptor();
                    myTask.Wait();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }

                Console.ReadLine();
            }

            private static Task<string> Interceptor()
            {
                Task<string> task = CoreLogic(); //Ignore


                Task unknownReturnType = task; //This is what I have access to. A Task object which can be one of numerous Task<TResult> types only known at runtime.

                Task continuation = unknownReturnType.ContinueWith(
                    t =>
                        {
                         if(someCondition)
                         {
                             throw new Exception("Error");
                         }

                         return t.Result; //This obviously does not work since we don't know the result. 
                    });

                return continuation;
            }


            private static async Task<string> CoreLogic()
            {
                return "test";
            }
        }
    }

Another way to express the problem.

  1. I can only change what is inside DoExtraValidation().
  2. I cannot change the signature of DoExtraValidation() to use generics.

How do I change DoExtraValidation to make it work for any Task< TResult > return type?

using System;
using System.Threading.Tasks;

namespace TaskContinueWith
{
    interface IServiceContract
    {
        Task<string> DoWork();
    }

    public class Servce : IServiceContract
    {
        public Task<string> DoWork()
        {
            var task = Task.FromResult("Hello");
            return  (Task<string>) DoExtraValidation(task);
        }

        private static Task DoExtraValidation(Task task)
        {
            Task returnTask = null;
            if (task.GetType() == typeof(Task<string>))
            {
                var knownType = task as Task<string>;
                returnTask = task.ContinueWith(
                    t =>
                    {
                        if(new Random().Next(100) > 50)
                        {
                            throw new Exception("Error");
                        }
                        return knownType.Result;

                    });
            }
            return returnTask;
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            try
            {
                IServiceContract myService = new Servce();
                Task<string> myTask = myService.DoWork();
                myTask.Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.ReadLine();
        }
    }
}
2
In order for your example to work, the unknownReturnType Task must be a Task<string> since that is what your Interceptor() method returns. Is it always a Task<string>? If so, then a cast will work. If not, then you could return Task<object> and then the client code would need to figure out (somehow) what it was getting back. But my question would be--why don't you know the type that is being returned? It would be better to give a better description of what you're trying to do--there might be better solutions. - Matt Smith

2 Answers

5
votes

Sounds like a case for dynamic. I'll try to use as little dynamic as possible. First we define a strongly-typed helper:

static Task<TResult> SetContinuation<TResult>(Task<TResult> task)
{
    return task.ContinueWith(
        t =>
            {
             if(someCondition)
             {
                 throw new Exception("Error");
             }

             return t.Result;
        });
}

This function obviously works, but it requires TResult to be known. dynamiccan fill it in:

Task continuation = SetContinuation((dynamic)unknownReturnType);

I just tested that the binding works at runtime. Alternatively you can use reflection instead to invoke the helper (use MethodInfo.MakeGenericMethod and others).

1
votes

You could use generics (Interceptor is here an extension method):

public static class ProjectExtensions
{
    public static Task<T> Interceptor<T>(this Task<T> task)
    {
        Task<T> continuation = task.ContinueWith<T>(t =>
            {
            if(someCondition)
            {
                throw new Exception("Error");
            }
            return t.Result;
        });
        return continuation;
    }
}

This way a call can be of any type:

var t1 = new Task<string>(() => /* return string */).Interceptor();
var t2 = new Task<int>(() => /* return int */).Interceptor();