如何写一个“等待”的方法?

我最后查看了一下我有点“得到”的异步和等待关键字,但是我看到的所有例子都调用了。Net 框架,例如调用 HttpClient.GetStringAsync()这个

我不太清楚的是这样一个方法中发生了什么,以及我将如何编写我自己的“等待”方法。是否就像包装我想在 Task 中异步运行的代码并返回该代码那样简单?

53788 次浏览

这很简单

Task.Run(() => ExpensiveTask());

使之成为一种可以期待的方法:

public Task ExpensiveTaskAsync()
{
return Task.Run(() => ExpensiveTask());
}

这里重要的是返回一个任务。这个方法甚至不需要被标记为异步。(只要再读一点点就可以了)

这个可以叫做

async public void DoStuff()
{
PrepareExpensiveTask();
await ExpensiveTaskAsync();
UseResultsOfExpensiveTask();
}

请注意,这里的方法签名说的是 async,因为该方法可以将控制权返回给调用方,直到 ExpensiveTaskAsync()返回。此外,在这种情况下,昂贵意味着耗费时间,如网络请求或类似。为了将繁重的计算交给另一个线程,通常最好使用“旧”方法,即用于 GUI 应用程序的 System.ComponentModel.BackgroundWorkerSystem.Threading.Thread

是的,从技术上讲,您只需要从 async方法返回一个 TaskTask<Result>,就可以实现一个等待的方法。

这支持 基于任务的异步模式

然而,有几种实现 TAP 的方法。详细信息请参阅 实现基于任务的异步模式

(当然,所有这些实现仍然返回 TaskTask<Result>。)

我会怎么写我自己的“等待”方法。

返回 Task并不是唯一的方法。您可以选择创建一个 海关服务员(通过实现 GetAwaiterINotifyCompletion) ,这里有一个很好的阅读: “等待一切”。例子。NET API 返回自定义等待者: Task.Yield()Dispatcher.InvokeAsync

我有一些与自定义等待者 给你给你的职位,例如:

// don't use this in production
public static class SwitchContext
{
public static Awaiter Yield() { return new Awaiter(); }


public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion
{
public Awaiter GetAwaiter() { return this; }


public bool IsCompleted { get { return false; } }


public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem((state) => ((Action)state)(), continuation);
}


public void GetResult() { }
}
}


// ...


await SwitchContext.Yield();

我将如何编写自己的“等待”方法?是否就像在 Task中异步运行代码并返回它一样简单?

这是一种选择,但它很可能不是您想要做的,因为它实际上并没有提供异步代码的许多优点。有关详细信息,请参阅 我应该为同步方法公开异步包装器吗

一般来说,方法是不可等待的,类别是。如果您希望能够编写类似于 await MyMethod()的内容,那么 MyMethod()必须返回 TaskTask<T>或自定义 awaitable 类型。使用自定义类型是一种罕见的高级场景; 使用 Task,您有几种选择:

  • 使用 asyncawait编写您的方法。这对于 作曲异步操作很有用,但是不能用于最内层的 awaitable 调用。
  • 使用 Task上的方法之一创建 Task,如 Task.Run()Task.FromAsync()
  • 使用 TaskCompletionSource。这是最一般的方法,它可以用来创建 awaitable 方法从任何将来发生的事情。

只要把你的方法转换成 Task,就像@Romiox 一样,我经常使用这个扩展:

public static partial class Ext
{
#region Public Methods
public static Task ToTask(Action action)
{
return Task.Run(action);
}
public static Task<T> ToTask<T>(Func<T> function)
{
return Task.Run(function);
}
public static async Task ToTaskAsync(Action action)
{
return await Task.Run(action);
}
public static async Task<T> ToTaskAsync<T>(Func<T> function)
{
return await Task.Run(function);
}
#endregion Public Methods
}

现在我们假设你有

void foo1()...


void foo2(int i1)...


int foo3()...


int foo4(int i1)...

...

然后您可以像@Romiox 那样声明您的[异步方法]

async Task foo1Async()
{
return await Ext.ToTask(() => foo1());
}
async Task foo2Async(int i1)
{
return await Ext.ToTask(() => foo2(i1));
}
async Task<int> foo3Async()
{
return await Ext.ToTask(() => foo3());
}
async Task<int> foo4Async(int i1)
{
return await Ext.ToTask(() => foo4(i1));
}

或者

async Task foo1Async()
{
return await Ext.ToTaskAsync(() => foo1());
}
async Task foo2Async(int i1)
{
return await Ext.ToTaskAsync(() => foo2(i1));
}
async Task<int> foo3Async()
{
return await Ext.ToTaskAsync(() => foo3());
}
async Task<int> foo4Async(int i1)
{
return await Ext.ToTaskAsync(() => foo4(i1));
}

...

现在您可以使用异步并等待任何 fooAsync 方法,例如 foo4Async

async Task<int> TestAsync () {
///Initial Code
int m = 3;
///Call the task
var X = foo4Async(m);
///Between
///Do something while waiting comes here
///..
var Result = await X;
///Final
///Some Code here
return Result;
}

如果不想使用 Task,可以编写一个完全自定义的等待对象。这样的对象是一个实现方法 GetAwaiter ()的对象,返回一个实现 INotifyCompletion的对象,该对象可以是对象本身。

更多: INotifyCompletion

服务员执行:

  • IsCompleted是获得国家
  • GetResult ()得到结果
  • 设置延续委托的 OnCompleted (Action continuation)

等待对象包含一些实际有效载荷的方法(例如,下面的方法是 Run)。

class Program {
// Need to change the declaration of Main() in order to use 'await'
static async Task Main () {
// Create a custom awaitable object
MyAwaitable awaitable = new MyAwaitable ();


// Run awaitable payload, ignore returned Task
_ = awaitable.Run ();


// Do some other tasks while awaitable is running
Console.WriteLine ("Waiting for completion...");


// Wait for completion
await awaitable;


Console.WriteLine ("The long operation is now complete. " + awaitable.GetResult());
}
}


public class MyAwaitable : INotifyCompletion {
// Fields
private Action continuation = null;
private string result = string.Empty;


// Make this class awaitable
public MyAwaitable GetAwaiter () { return this; }


// Implementation of INotifyCompletion for the self-awaiter
public bool IsCompleted { get; set; }
public string GetResult () { return result; }
public void OnCompleted (Action continuation) {
// Store continuation delegate
this.continuation = continuation;
Console.WriteLine ("Continuation set");
}


// Payload to run
public async Task Run () {
Console.WriteLine ("Computing result...");


// Wait 2 seconds
await Task.Delay (2000);
result = "The result is 10";


// Set completed
IsCompleted = true;


Console.WriteLine ("Result available");


// Continue with the continuation provided
continuation?.Invoke ();
}
}