没有T,在C#中将任务转换为任务

我有静态类充满扩展方法,其中每个方法都是异步的并返回一些值 – 如下所示:

public static class MyContextExtensions{ public static async Task SomeFunction(this DbContext myContext){ bool output = false; //...doing stuff with myContext return output; } public static async Task<List> SomeOtherFunction(this DbContext myContext){ List output = new List(); //...doing stuff with myContext return output; } } 

我的目标是能够从另一个类中的单个方法调用任何这些方法,并将其结果作为对象返回。 它看起来像这样:

 public class MyHub: Hub{ public async Task InvokeContextExtension(string methodName){ using(var context = new DbContext()){ //This fails because of invalid cast return await (Task)typeof(MyContextExtensions).GetMethod(methodName).Invoke(null, context); } } } 

问题是演员表失败了。 我的困境是我无法将任何类型参数传递给“InvokeContextExtension”方法,因为它是SignalR中心的一部分,并由javascript调用。 并且在某种程度上我不关心扩展方法的返回类型,因为它只是被序列化为JSON并被发送回javascript客户端。 但是我必须将Invoke返回的值强制转换为任务才能使用await运算符。 我必须提供一个带有“Task”的generics参数,否则它会将返回类型视为void。 所以这一切都归结为我如何成功地将具有generics参数T的Task转换为具有对象的generics参数的Task,其中T表示扩展方法的输出。

您可以分两步完成 – 使用基类await任务,然后使用reflection或dynamic收集结果:

 using(var context = new DbContext()) { // Get the task Task task = (Task)typeof(MyContextExtensions).GetMethod(methodName).Invoke(null, context); // Make sure it runs to completion await task.ConfigureAwait(false); // Harvest the result return (object)((dynamic)task).Result; } 

这是一个完整的运行示例,它将上述通过reflection调用Task技术置于上下文中:

 class MainClass { public static void Main(string[] args) { var t1 = Task.Run(async () => Console.WriteLine(await Bar("Foo1"))); var t2 = Task.Run(async () => Console.WriteLine(await Bar("Foo2"))); Task.WaitAll(t1, t2); } public static async Task Bar(string name) { Task t = (Task)typeof(MainClass).GetMethod(name).Invoke(null, new object[] { "bar" }); await t.ConfigureAwait(false); return (object)((dynamic)t).Result; } public static Task Foo1(string s) { return Task.FromResult("hello"); } public static Task Foo2(string s) { return Task.FromResult(true); } } 

通常, 要将Task转换为Task ,我只需要进行直接的连续映射:

 Task yourTaskT; // .... Task yourTaskObject = yourTaskT.ContinueWith(t => (object) t.Result); 

( 此处的文档链接 )


但是,您实际的特定需求是通过reflection调用Task并获取其(未知类型)结果

为此,您可以参考完整的dasblinkenlight的答案 ,它应该适合您的确切问题。

你不能将Task TaskTask ,因为Task不是协变的(它也不是逆变的)。 最简单的解决方案是使用更多reflection:

 var task = (Task) mi.Invoke (obj, null) ; var result = task.GetType ().GetProperty ("Result").GetValue (task) ; 

这是缓慢且低效的,但如果不经常执行此代码,则可以使用。 另外,如果您要阻止等待其结果,那么使用异步MakeMyClass1方法有什么用?

另一种可能性是为此目的编写扩展方法:

  public static Task Convert(this Task task) { TaskCompletionSource res = new TaskCompletionSource(); return task.ContinueWith(t => { if (t.IsCanceled) { res.TrySetCanceled(); } else if (t.IsFaulted) { res.TrySetException(t.Exception); } else { res.TrySetResult(t.Result); } return res.Task; } , TaskContinuationOptions.ExecuteSynchronously).Unwrap(); } 

它是无阻塞解决方案,将保留Task的原始状态/exception。

对于最佳方法 ,不使用reflection和动态丑陋的语法,并且不传递generics类型。 我会使用两种扩展方法来实现这一目标。

  public static async Task CastToObject([NotNull] this Task task) { return await task.ConfigureAwait(false); } public static async Task Cast([NotNull] this Task task) { return (TResult) await task.ConfigureAwait(false); } 

用法:

  Task task ... Task task2 = task.CastToObject().Cast(); 

这是我的第二种方法 ,但不推荐

 public static async Task Cast([NotNull] this Task task, TResult dummy = default) { return (TResult)(object) await task.ConfigureAwait(false); } 

用法:

 Task task ... Task task2 = task.Cast((T2) default); // Or Task task2 = task.Cast(); 

这是我的第三种方法 ,但不推荐 🙁类似于第二种方法)

 public static async Task Cast([NotNull] this Task task, Type type = null) { return (TResult)(object) await task.ConfigureAwait(false); } // Dummy type class public class Type { } public static class TypeExtension { public static Type ToGeneric(this T source) { return new Type(); } } 

用法:

 Task task ... Task task2 = task.Cast(typeof(T2).ToGeneric()); // Or Task task2 = task.Cast(); 

await与动态/reflection调用混合起来并不是一个好主意,因为await是一个编译器指令,它围绕调用的方法生成大量代码,并且没有真正意义“模拟”编译器使用更多的reflection,延续,包装等工作。 。

因为你需要的是在RUN TIME管理你的代码然后忘记在编译时工作的asyc await语法糖。 在没有它们的情况下重写SomeFunctionSomeOtherFunction ,并在运行时创建的自己的任务中启动操作。 您将获得相同的行为,但具有清晰的代码。

我根据dasblinkenlight的答案做了一个小扩展方法:

 public static class TaskExtension { public async static Task Cast(this Task task) { if (!task.GetType().IsGenericType) throw new InvalidOperationException(); await task.ConfigureAwait(false); // Harvest the result. Ugly but works return (T)((dynamic)task).Result; } } 

用法:

 Task task = ... Task = task.Cast(); 

这样,您可以将Task中的Task更改为您想要的任何内容。

最有效的方法是定制awaiter:

 struct TaskCast where TSource : TDestination { readonly Task task; public TaskCast(Task task) { this.task = task; } public Awaiter GetAwaiter() => new Awaiter(task); public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion { System.Runtime.CompilerServices.TaskAwaiter awaiter; public Awaiter(Task task) { awaiter = task.GetAwaiter(); } public bool IsCompleted => awaiter.IsCompleted; public TDestination GetResult() => awaiter.GetResult(); public void OnCompleted(Action continuation) => awaiter.OnCompleted(continuation); } } 

具有以下用途:

 Task<...> someTask = ...; await TaskCast<..., object>(someTask); 

这种方法的局限性在于结果不是Task而是一个等待对象。