我终于寻找到了异步及的await关键字,我有种“搞定”,但所有的例子我见过的异步调用.NET Framework中的方法,例如这一次,它调用HttpClient.GetStringAsync()
。
我不太清楚的是这种方法中发生了什么,以及如何编写自己的“ awaitable”方法。它就像将要异步运行的代码包装在Task中并返回该代码一样简单吗?
Answers:
就这么简单
Task.Run(() => ExpensiveTask());
使其成为一种等待的方法:
public Task ExpensiveTaskAsync()
{
return Task.Run(() => ExpensiveTask());
}
这里重要的是返回任务。该方法甚至不必标记为异步。(只需再读一点,便可以进入图片)
现在这可以称为
async public void DoStuff()
{
PrepareExpensiveTask();
await ExpensiveTaskAsync();
UseResultsOfExpensiveTask();
}
请注意,此处的方法签名为async
,因为该方法可能会将控制权返回给调用者,直到ExpensiveTaskAsync()
返回为止。同样,在这种情况下,昂贵意味着像Web请求之类的耗时。要将繁重的计算发送到另一个线程,通常最好使用“旧”方法,即System.ComponentModel.BackgroundWorker
用于GUI应用程序或System.Threading.Thread
。
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); }
就可以正常工作。
我将如何编写自己的“等待”方法?它像包装要在a中异步运行的代码
Task
并将其返回一样简单吗?
那是一种选择,但是它很可能不是您想要做的,因为它实际上并没有为您提供异步代码的许多优点。有关更多详细信息,请参见Stephen Toub的“我应该为异步方法公开异步包装程序吗?”。
通常,方法是不可接受的,类型是不可接受的。如果你希望能够写类似await MyMethod()
,则MyMethod()
必须返回Task
,Task<T>
或自定义await
能力类型。使用自定义类型是一种罕见的高级方案。使用Task
,您有几种选择:
async
和编写方法await
。这对于异步编写动作,但不能用于最内层await
调用。Task
对方法使用一个Task
,像Task.Run()
或Task.FromAsync()
。TaskCompletionSource
。这是最通用的方法,可用于await
根据将来发生的任何事情创建有能力的方法。...我将如何编写自己的“等待”方法。
返回aTask
不是唯一的方法。您可以选择创建一个自定义的等待者(通过实现GetAwaiter
和INotifyCompletion
),在这里可以很方便地阅读:“等待任何东西”。返回自定义等待者的.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();
// don't use this in production
-为什么呢?
await
s中不可用的finally
s及其后果,无论如何这些都不再适用。其余的全部都是投机性的,例如外观不好的代码(Task::StartNew
每个部分而不是await YieldTo
?,仅当您自己没有尝试过时),或者语义上不清楚(与ConfigureAwait(false)
我假设的相反)。
是的,从技术上讲,您只需要返回Task
或Task<Result>
从即可返回async
方法中即可实现可等待的方法。
这支持基于任务的异步模式。
但是,有几种实现TAP的方法。请参阅实现基于任务的异步模式详细信息,。
(但是,所有这些实现当然仍然会返回Task
或Task<Result>
。)
只需将您的方法转换为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));
}
...
现在,您可以使用async并等待任何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
,该对象可以是对象本身。
侍者实现:
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<T>
当然的async方法。异步可以很好地组合。