211

我读过的每一篇博文都告诉你如何在 C# 中使用异步方法,但出于某种奇怪的原因,从未解释过如何构建自己的异步方法来使用。所以我现在有这段代码使用我的方法:

private async void button1_Click(object sender, EventArgs e)
{
    var now = await CountToAsync(1000);
    label1.Text = now.ToString();
}

我写了这个方法CountToAsync

private Task<DateTime> CountToAsync(int num = 1000)
{
    return Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < num; i++)
        {
            Console.WriteLine("#{0}", i);
        }
    }).ContinueWith(x => DateTime.Now);
}

这是Task.Factory编写异步方法的最佳方式,还是我应该以另一种方式编写?

4

3 回答 3

250

StartNew除非您需要那种复杂程度,否则我不建议您这样做。

如果您的异步方法依赖于其他异步方法,最简单的方法是使用async关键字:

private static async Task<DateTime> CountToAsync(int num = 10)
{
  for (int i = 0; i < num; i++)
  {
    await Task.Delay(TimeSpan.FromSeconds(1));
  }

  return DateTime.Now;
}

如果您的异步方法正在做 CPU 工作,您应该使用Task.Run

private static async Task<DateTime> CountToAsync(int num = 10)
{
  await Task.Run(() => ...);
  return DateTime.Now;
}

您可能会发现我的async/await介绍很有帮助。

于 2013-04-17T15:30:59.980 回答
14

如果您不想在方法中使用 async/await,但仍要“装饰”它以便能够从外部使用 await 关键字,TaskCompletionSource.cs

public static Task<T> RunAsync<T>(Func<T> function)
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ =>          
    { 
        try 
        {  
           T result = function(); 
           tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
   }); 
   return tcs.Task; 
}

从这里这里

为了支持这样的 Task 范式,我们需要一种方法来保留 Task 外观和将任意异步操作称为 Task 的能力,但要根据提供异步,并以不显着成本的方式这样做。这就是 TaskCompletionSource 的目的。

我看到它也用于 .NET 源代码,例如WebClient.cs

    [HostProtection(ExternalThreading = true)]
    [ComVisible(false)]
    public Task<string> UploadStringTaskAsync(Uri address, string method, string data)
    {
        // Create the task to be returned
        var tcs = new TaskCompletionSource<string>(address);

        // Setup the callback event handler
        UploadStringCompletedEventHandler handler = null;
        handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.UploadStringCompleted -= completion);
        this.UploadStringCompleted += handler;

        // Start the async operation.
        try { this.UploadStringAsync(address, method, data, tcs); }
        catch
        {
            this.UploadStringCompleted -= handler;
            throw;
        }

        // Return the task that represents the async operation
        return tcs.Task;
    }

最后,我还发现以下有用:

我一直被问到这个问题。这意味着一定有某个线程阻塞了对外部资源的 I/O 调用。所以,异步代码释放了请求线程,但代价是系统中其他地方的另一个线程,对吧?一点都不。

为了理解为什么异步请求会扩展,我将跟踪一个(简化的)异步 I/O 调用示例。假设一个请求需要写入一个文件。请求线程调用异步写入方法。WriteAsync 由基类库 (BCL) 实现,并为其异步 I/O 使用完成端口。因此,WriteAsync 调用作为异步文件写入传递给操作系统。然后,操作系统与驱动程序堆栈通信,传递数据以写入 I/O 请求数据包 (IRP)。

这就是事情变得有趣的地方:如果设备驱动程序不能立即处理 IRP,它必须异步处理它。因此,驱动程序告诉磁盘开始写入并向操作系统返回“挂起”响应。操作系统将该“未决”响应传递给 BCL,然后 BCL 将不完整的任务返回给请求处理代码。请求处理代码等待任务,该任务从该方法返回一个不完整的任务,依此类推。最后,请求处理代码最终将一个不完整的任务返回给 ASP.NET,请求线程被释放以返回线程池。

ASP.NET 上的 Async/Await 简介

如果目标是提高可扩展性(而不是响应能力),那么这一切都依赖于提供这样做机会的外部 I/O 的存在。

于 2016-11-02T10:33:19.410 回答
2

使方法异步的一种非常简单的方法是使用 Task.Yield() 方法。正如 MSDN 所说:

您可以使用 await Task.Yield(); 在异步方法中强制方法异步完成。

将其插入方法的开头,然后它将立即返回给调用者并在另一个线程上完成该方法的其余部分。

private async Task<DateTime> CountToAsync(int num = 1000)
{
    await Task.Yield();
    for (int i = 0; i < num; i++)
    {
        Console.WriteLine("#{0}", i);
    }
    return DateTime.Now;
}
于 2020-04-06T16:35:24.913 回答