如何将同步代码包装成异步任务的最佳方法?

4

我正在实现一个异步接口方法(返回Task)。但是,我的实现必须是同步的。怎样最好地实现这一点?有没有内置的方法来做到这一点?以下是我正在考虑的几种选择:

  • Option 1: Task.FromResult

    return Task.FromResult(ComputeResult());
    

这样做的好处是我的代码可以同步运行。缺点是如果ComputeResult()失败或被取消,我的方法会抛出异常而不是返回一个失败的任务。

  • Option 2: Task.Run

    return Task.Run(() => ComputeResult());
    
这样可以更自然地传播失败和取消,但也会引入不必要的线程跳跃。
  • Option 3: TaskCompletionSource

    var tcs = new TaskCompletionSource<T>();
    try 
    { 
        tcs.SetResult(ComputeResult()); 
    }
    catch (OperationCanceledException) 
    { 
        tcs.SetCanceled(); 
    }
    catch (Exception ex) 
    { 
        tcs.SetException(ex); 
    }
    
    return tcs.Task;
    
这既可以传播失败/取消,又可以避免线程跳转,但它更冗长和复杂。

2
只有第二个选项对调用者实际上是异步的。问题是阻塞调用线程有多糟糕。假定接口使用任务,以便调用者不会被阻塞。 - Mike Zboray
“Thread-hop” 是什么意思? - eran otzap
结果能够快速且同步地完成吗?如果您可以非常快速地计算出结果,则“FromResult”可能是可以的。如果计算结果需要很长时间,考虑到调用者期望该操作是异步的,并且可能依赖于它,阻塞可能会导致问题。 - Servy
使用Thread.Run(),我的理解是工作不会在调用线程上运行,而是在另一个线程上运行,在调用方始终等待任务的情况下这是不必要的。 - ChaseMedallion
@whoisj,根据我的理解,ComputeResult没有返回 Task - noseratio - open to work
显示剩余4条评论
1个回答

2
你可能忽略了另外两个选项:
  • just make your method async and do return ComputeResult(). Suppress the compiler warning with pragma. If you don't like to suppress the warning, you could do this:

    async Task<Result> ComputeResultAsync()
    {
        await Task.FromResult(0);
        return ComputeResult();
    }
    
  • use Task.RunSynchronously:

    Task<Result> ComputeResultAsync()
    {
        var task = new Task<Result>(() => ComputeResult());
        task.RunSynchronously(TaskScheduler.Default);
        return task;
    }
    
后者将提供类似于异步方法的异常传播。然而,在某些条件下(比如堆栈太深时),RunSynchronously 仍可能以异步方式执行。

1
@YuvalItzchakov,我一直都在阅读模式下 :) 并不是我不活跃。只是每当我要发布一些东西时,通常已经有了一个高质量的答案! - noseratio - open to work
仍然需要获得金色的 async-await 徽章 :) - Yuval Itzchakov
1
@YuvalItzchakov,祝你好运 :) 在你的第一个新生儿到来之前抓住它 :) - noseratio - open to work

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接