我终于研究了异步 & await 关键字,我有点“get”,但我见过的所有示例都调用 .Net 框架中的异步方法,例如 this one,它调用 HttpClient.GetStringAsync()
。
我不太清楚的是这种方法会发生什么,以及我将如何编写自己的“等待”方法。是否像将我想在任务中异步运行的代码包装并返回一样简单?
Task
或 Task<T>
的异步方法。异步以这种方式可以很好地组合。
这很简单
Task.Run(() => ExpensiveTask());
使其成为可等待的方法:
public Task ExpensiveTaskAsync()
{
return Task.Run(() => ExpensiveTask());
}
这里重要的是返回一个任务。该方法甚至不必标记为异步。 (只需进一步阅读以使其进入图片)
现在这可以称为
async public void DoStuff()
{
PrepareExpensiveTask();
await ExpensiveTaskAsync();
UseResultsOfExpensiveTask();
}
请注意,这里的方法签名是 async
,因为该方法可能会将控制权返回给调用者,直到 ExpensiveTaskAsync()
返回。此外,在这种情况下,昂贵意味着耗时,例如 Web 请求或类似请求。要将繁重的计算发送到另一个线程,通常最好使用“旧”方法,即用于 GUI 应用程序的 System.ComponentModel.BackgroundWorker
或 System.Threading.Thread
。
我将如何编写自己的“等待”方法?是否像将我想在任务中异步运行的代码包装并返回一样简单?
这是一种选择,但它很可能不是您想要做的,因为它实际上并没有为您提供异步代码的许多优点。有关详细信息,请参阅Stephen Toub's Should I expose asynchronous wrappers for synchronous methods?
一般来说,方法是不可等待的,types 是。如果您希望能够编写类似 await MyMethod()
的内容,则 MyMethod()
必须返回 Task
、Task<T>
或自定义的 await
able 类型。使用自定义类型是一种罕见且高级的场景;使用 Task
,您有几个选项:
使用 async 和 await 编写您的方法。这对于异步组合操作很有用,但不能用于最内层的可等待调用。
使用 Task 上的一种方法创建 Task,例如 Task.Run() 或 Task.FromAsync()。
使用任务完成源。这是最通用的方法,它可用于从将来发生的任何事情中创建可等待的方法。
...我将如何编写自己的“等待”方法。
返回 Task
不是唯一的方法。您可以选择创建一个自定义等待器(通过实现 GetAwaiter
和 INotifyCompletion
),这是一个很好的阅读:"Await anything"。返回自定义等待程序的 .NET API 示例:Task.Yield()
、Dispatcher.InvokeAsync
。
我有一些带有自定义等待器 here 和 here 的帖子,例如:
// 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();
// don't use this in production
— 为什么?
finally
中不可用的 await
及其后果,无论如何都不再成立。其余的都是推测性的,比如看起来很糟糕的代码(Task::StartNew
每个部分而不是 await YieldTo
?只有你自己没有尝试过),或者语义不清楚(与 ConfigureAwait(false)
相反,我猜? )。
是的,从技术上讲,您只需要从 async
方法返回 Task
或 Task<Result>
即可实现可等待的方法。
这支持 Task-Based Asynchronous Pattern。
然而,有几种实现 TAP 的方法。有关详细信息,请参阅 Implementing the Task-based Asynchronous Pattern。
(当然,所有这些实现仍然返回 Task
或 Task<Result>
。)
只需将您的方法转换为任务。像@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 Method]
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 方法使用 async 和 await,例如 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
的对象,该对象可以是对象本身。
等待者实现:
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 ();
}
}
Task
或Task<>
的方法,这是正确的。但从技术上讲,您也可以编写一个返回YourOwnType
的方法,前提是YourOwnType
有一个名为GetAwaiter()
的公共无参数非静态实例方法,其返回类型是适当的(在其他地方查找详细信息)。所以await
有点像foreach
,它适用于任何具有合适公共方法的类型。foreach
需要IEnumerable
。当界面更适合该语言时,await
使用了鸭子类型,这让我有点失望。它是“供编译器使用”的事实是一个糟糕的借口。foreach
不需要任何接口!只需尝试这些类:class ForeachMe { public StrangeType GetEnumerator() { return new StrangeType(); } } class StrangeType { public bool MoveNext() { return true; } public DateTime Current { get { return DateTime.Now; } } }
使用它们,代码foreach (var x in new ForeachMe()) { Console.WriteLine(x); }
就可以正常工作。IEnumerable
或IEnumerable<>
。