如何在c#中创建异步方法
本文关键字:创建 异步方法 | 更新日期: 2023-09-27 18:09:45
我读过的每一篇博客文章都告诉你如何在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;
}
你可能会发现我的async
/await
介绍很有帮助。
如果你不想在你的方法中使用async/await,但仍然"decorate"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;
}
From here and here
要用Tasks来支持这样的范例,我们需要一种方法来保留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,它必须异步处理它。因此,驱动程序告诉磁盘开始写入,并向操作系统返回一个"pending"响应。操作系统将该"挂起"响应传递给BCL, BCL将一个未完成的任务返回给请求处理代码。请求处理代码等待任务,它从该方法返回一个未完成的任务,依此类推。最后,请求处理代码最终向ASP返回一个未完成的任务。. NET,请求线程被释放返回线程池。
ASP的Async/Await简介净
如果目标是提高可伸缩性(而不是响应性),这一切都依赖于外部I/O的存在,它提供了这样做的机会。
使方法异步的一个非常简单的方法是使用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;
}