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

时间:2014-02-11 11:41:47

标签: c# async-await

我终于看到了异步和放大器等待关键字,我有点“得到”,但我见过的所有例子都在.Net框架中称为异步方法,例如this one,调用HttpClient.GetStringAsync()

我不太清楚这种方法会发生什么,以及我如何编写自己的“等待”方法。它是否包装我想在Task中异步运行的代码并返回它?

6 个答案:

答案 0 :(得分:77)

就像

一样简单
Task.Run(() => ExpensiveTask());

使其成为一种等待的方法:

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

这里重要的是返回任务。该方法甚至不必标记为异步。 (只是进一步阅读它进入图片)

现在可以将其称为

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

请注意,此处方法签名为async,因为该方法可能会将控制权返回给调用者,直到ExpensiveTaskAsync()返回。而且,在这种情况下昂贵意味着耗时,如网络请求或类似的。要将繁重的计算发送到另一个线程,通常最好使用“旧”方法,即System.ComponentModel.BackgroundWorker用于GUI应用程序或System.Threading.Thread

答案 1 :(得分:12)

  

我如何编写自己的“等待”方法?它是否包装我希望在Task中异步运行并返回的代码?

这是一个选项,但它很可能不是您想要做的,因为它实际上并没有为您提供异步代码的许多优点。有关详细信息,请参阅Stephen Toub's Should I expose asynchronous wrappers for synchronous methods?

一般来说,方法是不可取的,类型是。如果您希望能够撰写await MyMethod()之类的内容,则MyMethod()必须返回TaskTask<T>或自定义await类型。使用自定义类型是一种罕见的高级方案;使用Task,您有几种选择:

  • 使用asyncawait撰写您的方法。这对于异步编写操作非常有用,但它不能用于最内部await次调用。
  • 使用Task上的一种方法创建Task,例如Task.Run()Task.FromAsync()
  • 使用TaskCompletionSource。这是最通用的方法,它可以用来创建将来会发生的任何事情的await方法。

答案 2 :(得分:10)

  

......我将如何编写自己的“等待”方法。

返回Task不是唯一的方法。您可以选择创建自定义等待者(通过实施GetAwaiterINotifyCompletion),这是一个很好的阅读:"Await anything"。返回自定义等待者的.NET API示例:Task.Yield()Dispatcher.InvokeAsync

我有一些帖子包含自定义等待者herehere,例如:

// 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();

答案 3 :(得分:4)

是的,从技术上讲,您只需要从Task方法返回Task<Result>async即可实现等待方法。

这支持Task-Based Asynchronous Pattern

然而,有几种方法可以实现TAP。有关详细信息,请参阅Implementing the Task-based Asynchronous Pattern

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

答案 4 :(得分:1)

只需将您的方法转换为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)
    {
        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));
}

OR

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;
}

答案 5 :(得分:0)

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

更多:INotifyCompletion

侍者实现:

  • IsCompleted获得状态
  • GetResult ()获得结果
  • OnCompleted (Action continuation)设置延续委托。

awaitable对象包含一些用于实际有效负载的方法(例如,下面的方法是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 ();
    }
}