如何在C#中创建异步方法?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (49)

我读过的每一篇博客文章都告诉如何使用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,编写异步方法的最佳方法,还是应该以另一种方式编写?

提问于
用户回答回答于

我不推荐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;
}
用户回答回答于

如果不想在方法中使用异步/等待,但仍然“装饰”它,以便能够使用等待关键字从外部。

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的能力,但需要根据提供异步的底层基础结构的规则来控制该任务的生存期,并且以一种不太昂贵的方式进行。这就是TaskCompletionSource的目的。

我看到也在.NET源代码中使用例如。

    [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由基类库(BaseClassLibrary,BCL)实现,并使用完成端口作为异步I/O。因此,WriteAsync调用作为异步文件写入传递到操作系统。然后,OS与驱动程序栈通信,传递数据以写入I/O请求包(IRP)。这就是事情变得有趣的地方:如果设备驱动程序不能立即处理IRP,它必须异步处理它。因此,驱动程序告诉磁盘开始写入,并向操作系统返回一个“挂起”的响应。操作系统将“挂起”响应传递给bcl,bcl将一个不完整的任务返回给请求处理代码。请求处理代码等待该任务,该任务从该方法返回一个不完整的任务,以此类推。最后,请求处理代码将一个不完整的任务返回给ASP.NET,请求线程被释放回线程池。

如果目标是提高可伸缩性(而不是响应性),那么它都依赖于外部I/O的存在,它提供了这样做的机会。

扫码关注云+社区