编写自己的async方法
本文关键字:方法 async 自己的 | 更新日期: 2023-09-27 18:14:53
我想知道如何写自己的异步方法的"正确"的方式。
我看过很多解释async/await模式的帖子,像这样:
http://msdn.microsoft.com/en-us/library/hh191443.aspx// Three things to note in the signature:
// - The method has an async modifier.
// - The return type is Task or Task<T>. (See "Return Types" section.)
// Here, it is Task<int> because the return statement returns an integer.
// - The method name ends in "Async."
async Task<int> AccessTheWebAsync()
{
// You need to add a reference to System.Net.Http to declare client.
HttpClient client = new HttpClient();
// GetStringAsync returns a Task<string>. That means that when you await the
// task you'll get a string (urlContents).
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
// The await operator suspends AccessTheWebAsync.
// - AccessTheWebAsync can't continue until getStringTask is complete.
// - Meanwhile, control returns to the caller of AccessTheWebAsync.
// - Control resumes here when getStringTask is complete.
// - The await operator then retrieves the string result from getStringTask.
string urlContents = await getStringTask;
// The return statement specifies an integer result.
// Any methods that are awaiting AccessTheWebAsync retrieve the length value.
return urlContents.Length;
}
private void DoIndependentWork()
{
resultsTextBox.Text += "Working........'r'n";
}
对于任何已经实现此功能的。net方法(如
)都非常有效。- 系统。IO提供备件 数据库提供备件
- 网络相关操作(下载、上传…)
但是,如果我想编写自己的方法,需要花费相当长的时间来完成,而没有方法可以使用,并且重载在上面示例的DoIndependentWork
方法中,该怎么办?
在这个方法中我可以这样做:
- 字符串操作 计算
- 处理我自己的对象
- 聚合、比较、过滤、分组、处理内容
- 列表操作,添加,删除,复制
我又一次偶然发现很多帖子里的人都是这样做的(还是以上面的例子为例):
async Task<int> AccessTheWebAsync()
{
HttpClient client = new HttpClient();
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
await DoIndependentWork();
string urlContents = await getStringTask;
return urlContents.Length;
}
private Task DoIndependentWork()
{
return Task.Run(() => {
//String manipulations
//Calculations
//Handling my own objects
//Aggregating, comparing, filtering, grouping, handling stuff
//List operations, adding, removing, coping
});
}
您可能会注意到这些变化是DoIndependentWork
现在返回一个任务,而在AccessTheWebAsync
任务中该方法获得了一个await
。
重载操作现在被封装在Task.Run()
中,这是所有需要的吗?如果这就是它所需要的,我需要做的唯一一件事就是为我的库中的每个方法提供async方法,如下所示:
public class FooMagic
{
public void DoSomeMagic()
{
//Do some synchron magic...
}
public Task DoSomeMagicAsync()
{
//Do some async magic... ?!?
return Task.Run(() => { DoSomeMagic(); });
}
}
如果你能给我解释一下就好了,因为即使是这样一个高投票的问题:如何编写简单的异步方法?只是用已经存在的方法来解释它,只是使用asynn/await模式,就像上面提到的问题的评论一样:如何编写简单的异步方法?
实际回答
你可以使用TaskCompletionSource
,它有一个承诺任务,不执行任何代码,只有:
"表示未绑定到委托的任务的生产者端,通过Task属性提供对消费者端的访问。"
在启动异步操作时将该任务返回给调用者,并在结束操作时设置结果(或异常/取消)。确保操作是真正异步的是你的责任。
下面是Stephen Toub的AsyncManualResetEvent
实现中所有async方法的根的一个很好的例子:
class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return _tcs.Task; }
public void Set() { _tcs.TrySetResult(true); }
public void Reset()
{
while (true)
{
var tcs = _tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
的背景使用async-await
主要有两个原因:
- 改进的可伸缩性:当你有
I/O
密集的工作(或其他固有的异步操作)时,你可以异步调用它,所以你释放调用线程,它能够在同时做其他工作。 - Offloading:当你有
CPU
密集的工作时,你可以异步调用它,这将工作从一个线程转移到另一个线程(主要用于GUI
线程)。
所以大多数。Net
框架的异步调用支持async
开箱即用,卸载时使用Task.Run
(如示例中所示)。当您创建一个新的异步调用(例如I/O
或异步同步构造)时,您实际上需要来自己实现async
的唯一情况是。
这些情况是非常罕见的,这就是为什么你找到的答案大多是
"仅使用
async/await
模式并使用已有方法来解释它"
你可以深入了解TaskCompletionSource的性质
如果你能解释给我听就太好了:如何写简单的异步方法?
首先,我们需要理解async
方法的含义。当一个人将async
方法暴露给使用async
方法的最终用户时,你是在告诉他:"听着,这个方法将很快返回给你,并承诺在不久的将来某个时候完成。"这是你向用户保证的。
现在,我们需要了解Task
如何使这一"承诺"成为可能。正如您在问题中所问的,为什么简单地在我的方法中添加Task.Run
使得使用await
关键字等待它有效?
Task
实现GetAwaiter
模式,这意味着它返回一个名为awaiter
的对象(它实际上被称为TaskAwaiter
)。TaskAwaiter
对象实现INotifyCompletion
或ICriticalNotifyCompletion
接口,暴露OnCompleted
方法。
await
关键字,编译器就会依次使用所有这些好东西。编译器将确保在设计时,您的对象实现GetAwaiter
,并反过来使用它将代码编译成状态机,这将使您的程序能够在等待时将控制返回给调用者,并在工作完成时恢复。
现在,有一些指导方针要遵循。一个真正的异步方法不会在后台使用额外的线程来完成它的工作(Stephan Cleary在There Is No Thread中解释得很好),这意味着暴露一个在内部使用Task.Run
的方法对api的消费者有点误导,因为他们会假设你的任务中没有涉及额外的线程。你应该做的是同步地公开你的API,让用户自己使用Task.Run
卸载它,控制执行流程。
async
方法主要用于I/O Bound操作,因为这些方法在执行IO操作时自然不需要消耗任何线程,这就是为什么我们经常在负责IO操作的类中看到它们的原因,例如硬盘驱动器调用,网络调用等。
我建议阅读并行PFX团队文章我应该为同步方法公开异步包装器吗?它确切地说明了你要做什么,以及为什么不建议这么做。
TL;DR:
Task.Run()
是你想要的,但要小心隐藏它在你的库
我可能是错的,但是你可能正在寻找如何让cpu绑定的代码异步运行的指导[Stephen Cleary]。我也很难找到这个,我认为它如此困难的原因是这不是你应该为图书馆做的- kinda…
条链接的文章是一个很好的阅读(5-15分钟,取决于),其中详细介绍了如何以及为什么使用Task.Run()
作为API的一部分,而不是使用它来不阻塞UI线程-并区分了两种"类型"的长时间运行的进程,人们喜欢异步运行:
- cpu绑定进程(一个正在处理/实际工作,需要一个单独的线程来完成它的工作)
- 真正的异步操作(有时称为io绑定-在操作之间做一些事情,并且在操作之间有一堆等待时间,并且最好不要占用线程,而它坐在那里什么都不做)。
这篇文章涉及到API函数在各种上下文中的使用,并解释了相关的体系结构是"偏爱"同步方法还是异步方法,以及具有同步和异步方法签名的API在开发人员看来是怎样的。
最后一节"好了,关于错误的解决方案够了吗?我们如何以正确的方式解决这个问题??"进入我认为你问的问题,以这个结尾:
结论:不要使用Task。运行中实现的方法;相反,使用Task。
基本上,Task.Run()
占用了一个线程,因此是用于cpu密集型工作的东西,但它归结为,其中使用。当你试图做一些需要大量工作的事情,你不想阻塞UI线程,使用Task.Run()
直接运行困难的函数(即,在事件处理程序或基于UI的代码中):
class MyService
{
public int CalculateMandelbrot()
{
// Tons of work to do in here!
for (int i = 0; i != 10000000; ++i)
;
return 42;
}
}
...
private async void MyButton_Click(object sender, EventArgs e)
{
await Task.Run(() => myService.CalculateMandelbrot());
}
但是…不要将Task.Run()
隐藏在以-Async
为后缀的API函数中,因为基本上每个-Async
函数都是真正异步的,而不是cpu绑定的。
// Warning: bad code!
class MyService
{
public int CalculateMandelbrot()
{
// Tons of work to do in here!
for (int i = 0; i != 10000000; ++i)
;
return 42;
}
public Task<int> CalculateMandelbrotAsync()
{
return Task.Run(() => CalculateMandelbrot());
}
}
换句话说,不要调用cpu绑定的函数-Async
,因为用户会认为它是io绑定的——只需使用Task.Run()
异步调用它,并让其他用户在他们认为合适的时候也这样做。或者,将其命名为对您有意义的其他名称(例如BeginAsyncIndependentWork()
或StartIndependentWorkTask()
)。