C# 编写自己的异步方法
我想知道如何以“正确”的方式编写自己的异步方法 我已经看到很多文章解释了异步/等待模式,如下所示: 您可能会注意到,更改是C# 编写自己的异步方法,c#,.net,asynchronous,task-parallel-library,async-await,C#,.net,Asynchronous,Task Parallel Library,Async Await,我想知道如何以“正确”的方式编写自己的异步方法 我已经看到很多文章解释了异步/等待模式,如下所示: 您可能会注意到,更改是DoIndependentWork现在返回一个任务,并且在AccessTheWebAsync任务中,该方法得到了一个wait 重载操作现在被封装在一个任务中。Run(),只需要这些吗? 如果要为库中的每一个方法提供异步方法,我只需要这样做,请执行以下操作: public class FooMagic { public void DoSomeMagic() {
DoIndependentWork
现在返回一个任务,并且在AccessTheWebAsync
任务中,该方法得到了一个wait
重载操作现在被封装在一个任务中。Run()
,只需要这些吗?
如果要为库中的每一个方法提供异步方法,我只需要这样做,请执行以下操作:
public class FooMagic
{
public void DoSomeMagic()
{
//Do some synchron magic...
}
public Task DoSomeMagicAsync()
{
//Do some async magic... ?!?
return Task.Run(() => { DoSomeMagic(); });
}
}
如果你能给我解释一下就好了,因为即使是这样一个投票率很高的问题:
仅使用现有的方法解释,并且仅使用asyn/WAIT模式,如对上述问题的评论,将其带到要点:
实际答案
您可以使用,它有一个不执行任何代码的
表示未绑定到委托的任务的生产者端,通过任务属性提供对使用者端的访问
启动异步操作时将该任务返回给调用者,并在结束时设置结果(或异常/取消)。确保操作是真正异步的是您自己
下面是一个很好的示例,说明了这种实现中的所有异步方法的根:
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;
}
}
}
类异步手动重置事件
{
私有易失性TaskCompletionSource_tcs=新TaskCompletionSource();
公共任务WaitAsync(){return\u tcs.Task;}
public void Set(){u tcs.TrySetResult(true);}
公共无效重置()
{
while(true)
{
var tcs=_tcs;
如果(!tcs.Task.IsCompleted | |
Interlocked.CompareExchange(ref _tcs,new TaskCompletionSource(),tcs)=tcs)
返回;
}
}
}
背景
使用async wait
基本上有两个原因:
I/O
密集型工作(或其他固有的异步操作)时,您可以异步调用它,因此您可以释放调用线程,同时它还可以执行其他工作CPU
密集型工作时,您可以异步调用它,这将工作从一个线程转移到另一个线程(主要用于GUI
线程)Net
框架的大多数异步调用都支持开箱即用的async
,用于卸载您使用的Task.Run
(如您的示例所示)。只有在创建一个新的异步调用(I/O
或async)时,才需要自己实现async
这些案例极为罕见,这就是为什么你大部分都能找到这样的答案
“仅使用现有的方法和使用async/await
模式解释”
你可以深入研究 如果你能给我解释一下:如何编写简单的异步 方法 首先,我们需要理解
async
方法的含义。当一个人向使用async
方法的最终用户公开async
方法时,你告诉他:“听着,这个方法会很快回到你身边,并承诺在不久的将来完成。”。这就是你向用户保证的
现在,我们需要了解Task
是如何使这一“承诺”成为可能的。正如您在问题中所问的,为什么只在我的方法中添加一个Task.Run
,就可以使用await
关键字有效地等待
Task
实现了GetAwaiter
模式,这意味着它返回一个名为awaiter
的对象(实际调用的对象)。TaskAwaiter
对象实现或接口,公开一个OnCompleted
方法
一旦使用了wait
关键字,编译器就会依次使用所有这些好东西。编译器将确保在设计时,您的对象实现了GetAwaiter
,并反过来使用它将代码编译成状态机,这将使您的程序能够在等待后将控制权交还给调用方,并在工作完成后恢复
现在,有一些指导方针需要遵循。真正的异步方法不会在幕后使用额外的线程来完成其工作(Stephan Cleary在中对此进行了精彩的解释),这意味着公开一个内部使用Task.Run
的方法对api的使用者来说有点误导,因为他们会假定您的任务中不涉及额外的线程。你应该做的是同步公开你的API,让用户使用任务卸载它。自己运行,控制执行流
async
方法主要用于I/O绑定的操作,因为在执行IO操作时,这些方法自然不需要消耗任何线程,这就是为什么我们在负责执行IO操作的类中看到它们的原因,例如硬盘驱动器调用、网络调用等
我建议阅读《并行PFX团队》一文,该文准确地讲述了您正在尝试做什么以及为什么不推荐它。TL;博士:
Task.Run()
是您想要的,但要小心将其隐藏在库中
我可能错了,但你可能正在寻找斯蒂芬·克利里(Stephen Cleary)的指导。我也很难找到这本书,我想它之所以这么难,是因为这不是你应该为图书馆做的事——有点
文章
这篇链接文章读得很好(5-15分钟,视情况而定),详细介绍了将Task.Run()
作为API的一部分与使用它来阻止UI线程的方法和原因,并区分了两种“类型”的lon
public class FooMagic
{
public void DoSomeMagic()
{
//Do some synchron magic...
}
public Task DoSomeMagicAsync()
{
//Do some async magic... ?!?
return Task.Run(() => { DoSomeMagic(); });
}
}
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;
}
}
}
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());
}
// 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());
}
}