【问题标题】:How to create an awaitable method properly如何正确创建等待方法
【发布时间】:2017-09-09 15:06:10
【问题描述】:

我正在写一些等待的方法,我在互联网上找到了很多方法。所以我来到这里是为了了解每条道路上真正发生的事情,以及是否必须取消某些道路。

据我所知,有两种等待方法:

那些调用其他等待方法的:

public async Task<Foo> GetFooAsync()
{
    var foo = await TrulyGetFooAsync();

    // Do stuff with foo

    return foo;
}

我没有找到任何其他方法来做到这一点,我认为这是正确的方法。如果我错了,请告诉我!

那些只调用不可等待的方法:

我想到了这些问题。

例如,我看到了这个:

示例 1

public async Task<Foo> GetFooAsync()
{
    return await Task.Run(() => TrulyGetFoo());
}

据我了解,async/await 关键字是无用的,可以避免给出这个:

示例 2

public Task<Foo> GetFooAsync()
{
    return Task.Run(() => TrulyGetFoo());
}

这最后一个例子是我到现在为止所做的。关于这一点,是否有区别:

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

Task.Run((Foo)TrulyGetFoo); // I don't know if the cast is required at any time but in my code, it was

???

但我最近发现了这种方式:

示例 3

public Task<Foo> GetFooAsync()
{
    TaskCompletionSource<Foo> tcs = new TaskCompletionSource<Foo>();
    tcs.SetResult(TrulyGetFoo());
    return tcs.Task;
}

如果我理解正确,可等待的方法并不总是在另一个线程上运行???我的猜测是第三个示例提供了这种机制(但是如何???我在第三个示例中只看到同步代码),当示例 1 和 2 将始终在工作线程上运行时???

可能还有其他方法可以编写可等待的方法,所以请告诉我。

【问题讨论】:

  • 另一种方式:public Task GetFooAsync() { return TrulyGetFooAsync(); }
  • 是的,当然。我已经简化了这个例子太多,你的话是对的。我将编辑我的问题以澄清!
  • “我在第三个例子中只看到同步代码”——它是完全同步的。任务将返回已经完成的状态。在这种情况下,您可以对 return Task.FromResult(TrulyGetFoo()) 执行相同的操作。

标签: c# async-await


【解决方案1】:

我将它用于操作我的数据库的异步事件

private async void btnSave_Click(object sender, EventArgs e)
    {

             success = await someClass.someMethod(some args);

    }

而 someMethod 就是这样的任务:

public static async Task<someObject> someMethod(args)
    {
       //do something here
    }

【讨论】:

  • 这如何回答上面的问题?
  • async void 除非绝对必要,否则应避免使用。
【解决方案2】:

简短的版本是这样的:任何返回 Task 的东西都可以在 async 代码块中等待

public async Task MyAsyncMethod()
{
    // do some stuff
    await TrulyAsyncFoo();
    // do some other stuff
    return;
}

如果等待异步调用是该方法唯一要做的事情,您可以简单地返回任务本身,该任务将在“上游”等待:

public Task MyAsyncMethod()
{
    return TrulyAsyncFoo();
}

就在异步方法中调用同步(非异步)代码而言,这没什么。就像普通代码一样调用它:

public async Task MyAsyncMethod()
{
    MySyncMethod();
    await TrulyAsyncFoo();
    MyOtherSyncMethod();
}

执行Task.Run(() =&gt; Foo()) 几乎总是一种代码味道,表明您没有正确执行异步/等待。编写异步代码与编写多线程代码不同。异步只是告诉编译器您需要等待一些网络或 IO 绑定任务完成的好方法。

总结一下:

  • Await 让您可以并行编写异步和同步代码
  • 异步只能用于等待网络或 IO 密集型任务,而不是计算密集型任务
  • 异步方法应始终返回TaskTask&lt;T&gt;
  • 避免async void
  • 避免在 ASP.NET 和其他线程应用程序中使用 task.Wait()task.Result 进行阻塞,除非您知道自己在做什么

【讨论】:

  • async/await/task 的一个很好的资源是 Stephen Cleary 的博客blog.stephencleary.com/2014/04/…
  • If awaiting the async call is the only thing the method does, you cansimply return the task itself > 正如我在问题的 cmets 中对 Rufo 爵士所说的那样,我简化了太多示例。我刚刚编辑了我的问题以获得更好的示例。 There is usually no need for Task.Run(() =&gt; Foo()) or other complex syntax. That's almost always a code smell that you aren't doing async/await right > 正如你所说的usually。那如果我还需要它呢?
  • 来自 Rufo 爵士的链接:developers who have used Task in the past tend to try to use it the same way in the async world (which is wrong) 我想我不止一次落入陷阱。这两个链接真的很有趣。谢谢!
  • @fharreau 我稍微澄清了我的答案。调用同步方法不需要Task.Run - 具体来说,您要做什么?
  • 我正在以两种不同的方式实现一个接口,其中的方法返回一个任务。一种实现是在后台使用可等待的方法,所以我没有问题。另一个实现只使用不可等待的方法,那么我该如何编写这个实现呢?只是Task.FromResult() ?我正在处理的应用程序是 WPF 应用程序。我不希望我的 UI 在通话期间被冻结。
【解决方案3】:

例如,我看到 [Task.Run 同步代码的包装器]

This is a bad practice。我有一篇博文解释了why Task.Run should not be used as a method implementation

据我了解,async/await 关键字没用,可以避免

是的,但是I don't recommend eliding async/await in more complex methods

我最近发现 [TaskCompletionSource&lt;T&gt;]

如 cmets 中所述,您的代码示例仍然是同步的。

要使其异步,您的代码应该开始一些操作并返回TaskCompletionSource&lt;T&gt;.Task。然后,每当该操作完成时,您的完成处理程序应该调用TaskCompletionSource&lt;T&gt;.SetResult(或类似方法)。例如,请参阅TAP wrappers for EAPTAP wrappers for WaitHandles

TaskFactory.FromAsync 也是TaskCompletionSource&lt;T&gt; 的包装器,用于TAP wrappers for APM

【讨论】:

  • 我正在阅读您的 A Tour of Task 文章,由 Rufo 爵士在另一个答案中提供。这个,加上你答案中的所有链接,我今天有一堆有趣的阅读材料。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-30
  • 2018-02-04
  • 1970-01-01
  • 1970-01-01
  • 2010-09-24
  • 2014-11-15
  • 2022-12-05
相关资源
最近更新 更多