【问题标题】:How to convert a Task<TDerived> to a Task<TBase>?如何将 Task<TDerived> 转换为 Task<TBase>?
【发布时间】:2013-03-09 22:25:13
【问题描述】:

由于 C# 的 Task 是一个类,你显然不能将 Task&lt;TDerived&gt; 转换为 Task&lt;TBase&gt;

但是,你可以这样做:

public async Task<TBase> Run() {
    return await MethodThatReturnsDerivedTask();
}

是否有我可以调用的静态任务方法来获取Task&lt;TDerived&gt; 实例,它基本上只是指向底层任务并转换结果?我想要类似的东西:

public Task<TBase> Run() {
    return Task.FromDerived(MethodThatReturnsDerivedTask());
}

这样的方法存在吗?仅出于此目的使用异步方法是否有任何开销?

【问题讨论】:

    标签: c# task async-await


    【解决方案1】:

    你可以试试这个: task.ContinueWith&lt;TDerived&gt;( t =&gt; (TDerived)t.Result);

    【讨论】:

      【解决方案2】:

      这样的方法存在吗?仅出于此目的使用异步方法是否有任何开销?

      没有用于此的内置方法,这确实会导致开销。

      “最轻量级”的替代方法是使用TaskCompletionSource&lt;T&gt; 为此创建一个新任务。这可以通过像这样的扩展方法来完成:

      static Task<TBase> FromDerived<TBase, TDerived>(this Task<TDerived> task) where TDerived : TBase
      {
           var tcs = new TaskCompletionSource<TBase>();
      
           task.ContinueWith(t => tcs.SetResult(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
           task.ContinueWith(t => tcs.SetException(t.Exception.InnerExceptions), TaskContinuationOptions.OnlyOnFaulted);
           task.ContinueWith(t => tcs.SetCanceled(), TaskContinuationOptions.OnlyOnCanceled);
      
           return tcs.Task;
      }
      

      【讨论】:

      • 考虑通过 SetException(t.Exception.InnerExceptions) 来处理异常分支以避免 AggregateException 包装器。
      • 您的意思是使用TaskContinuationOptions 而不是TaskCompletionOptions
      【解决方案3】:

      有这样的方法吗?

      没有。

      仅为此目的使用异步方法是否有任何开销?

      是的。但这是最简单的解决方案。

      请注意,更通用的方法是Task 的扩展方法,例如Then。 Stephen Toub 探索了这个in a blog post,而我最近又发现了incorporated it into AsyncEx

      使用Then,您的代码将如下所示:

      public Task<TBase> Run()
      {
        return MethodThatReturnsDerivedTask().Then(x => (TBase)x);
      }
      

      另一种开销略少的方法是创建自己的 TaskCompletionSource&lt;TBase&gt; 并使用派生结果完成它(在我的 AsyncEx 库中使用 TryCompleteFromCompletedTask):

      public Task<TBase> Run()
      {
        var tcs = new TaskCompletionSource<TBase>();
        MethodThatReturnsDerivedTask().ContinueWith(
            t => tcs.TryCompleteFromCompletedTask(t),
            TaskContinuationOptions.ExecuteSynchronously);
        return tcs.Task;
      }
      

      或者(如果你不想依赖 AsyncEx):

      public Task<TBase> Run()
      {
        var tcs = new TaskCompletionSource<TBase>();
        MethodThatReturnsDerivedTask().ContinueWith(t =>
        {
          if (t.IsFaulted)
            tcs.TrySetException(t.Exception.InnerExceptions);
          else if (t.IsCanceled)
            tcs.TrySetCanceled();
          else
            tcs.TrySetResult(t.Result);
        }, TaskContinuationOptions.ExecuteSynchronously);
        return tcs.Task;
      }
      

      【讨论】:

      • 为什么不只是return MethodThatReturnsDerivedTask().ContinueWith(task =&gt; (TBase)task.Result, TaskContinuationOptions.ExecuteSynchronously)
      • 如果您使用Result,您将在AggregateException 中包装所有异常,如果原始任务在取消状态下完成,则完成错误。也就是说,使用更多代码来正确处理这些情况,您可以使用ContinueWith 而不使用TaskCompletionSource 来制定可行的解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-19
      • 2019-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多