我正在尝试用新语法替换旧的“即发即弃”调用,希望更简单,似乎在逃避我。这是一个例子
class Program
{
static void DoIt(string entry)
{
Console.WriteLine("Message: " + entry);
}
static async void DoIt2(string entry)
{
await Task.Yield();
Console.WriteLine("Message2: " + entry);
}
static void Main(string[] args)
{
// old way
Action<string> async = DoIt;
async.BeginInvoke("Test", ar => { async.EndInvoke(ar); ar.AsyncWaitHandle.Close(); }, null);
Console.WriteLine("old-way main thread invoker finished");
// new way
DoIt2("Test2");
Console.WriteLine("new-way main thread invoker finished");
Console.ReadLine();
}
}
两种方法都做同样的事情,但是我似乎已经获得了(不需要EndInvoke
和关闭句柄,这仍然有点争议)我正在以新的方式失去等待一个Task.Yield()
,这实际上提出了一个新问题,即必须重写所有现有的异步F&amp; F方法才能添加一个单行。在性能/清理方面是否有一些无形的收获?
如果我无法修改背景方法,我将如何应用异步?在我看来,没有直接的方法,我将不得不创建一个等待Task.Run()的包装器异步方法?
编辑:我现在看到我可能会错过一些真正的问题。问题是:给定一个同步方法A(),如何使用async
/ await
以一种即发即忘的方式异步调用它,而不会得到比“旧”更复杂的解决方案方式“
答案 0 :(得分:76)
避免使用async void
。它有关于错误处理的棘手语义;我知道有些人称之为“火与忘记”,但我通常会使用“火灾和崩溃”一词。
问题是:给定一个同步方法A(),如何使用async / await以一种即发即忘的方式异步调用它,而不会得到比“旧方法”更复杂的解决方案
您不需要async
/ await
。只需将其称为:
Task.Run(A);
答案 1 :(得分:46)
正如其他答案所述,以及优秀的blog post,您希望避免在UI事件处理程序之外使用async void
。如果你想要一个安全“开火并忘记”async
方法,请考虑使用这种模式(归功于@ReedCopsey;这种方法是他在聊天对话中给我的方法):
为Task
创建扩展方法。它运行传递的Task
并捕获/记录任何异常:
static async void FireAndForget(this Task task)
{
try
{
await task;
}
catch (Exception e)
{
// log errors
}
}
创建时始终使用Task
样式async
方法,而不是async void
。
以这种方式调用这些方法:
MyTaskAsyncMethod().FireAndForget();
您不需要await
它(也不会生成await
警告)。它还会处理正确的任何错误,因为这是您放置async void
的唯一地方,您不必记得在任何地方放置try/catch
块。
如果您确实想要async
正常,这也为您提供不选项,使用await
方法作为“即发即弃”方法。
答案 2 :(得分:18)
对我而言,似乎“等待”某些东西并“发射并忘记”是两个正交的概念。您可以异步启动方法而不关心结果,或者您希望在操作完成后继续在原始上下文上执行(并且可能使用返回值),这正是await所做的。如果你只想在ThreadPool线程上执行一个方法(这样你的UI就不会被阻止),那就去找
Task.Factory.StartNew(() => DoIt2("Test2"))
你会没事的。
答案 3 :(得分:1)
我的感觉是,这些“即发即忘”的方法主要是需要一种干净的方式来交错UI和背景代码的工件,这样你仍然可以将逻辑编写为一系列顺序指令。由于async / await负责通过SynchronizationContext进行编组,因此这不再是一个问题。较长序列中的内联代码有效地成为以前从后台线程中的例程启动的“即发即忘”块。它实际上是对模式的反转。
主要区别在于awaits之间的块更类似于Invoke而不是BeginInvoke。如果你需要更像BeginInvoke的行为,你可以调用下一个异步方法(返回一个Task),然后实际上等待返回的Task直到你想要'BeginInvoke'的代码之后。
public async void Method()
{
//Do UI stuff
await SomeTaskAsync();
//Do more UI stuff (as if called via Invoke from a thread)
var nextTask = NextTaskAsync();
//Do UI stuff while task is running (as if called via BeginInvoke from a thread)
await nextTask;
}
答案 4 :(得分:0)
这是我根据 Ben Adams 关于构建这样一个结构的推文整理的一个类。 HTHhttps://twitter.com/ben_a_adams/status/1045060828700037125
using Microsoft.Extensions.Logging;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
// ReSharper disable CheckNamespace
namespace System.Threading.Tasks
{
public static class TaskExtensions
{
[SuppressMessage("ReSharper", "VariableHidesOuterVariable", Justification = "Pass params explicitly to async local function or it will allocate to pass them")]
public static void Forget(this Task task, ILogger logger = null, [CallerMemberName] string callingMethodName = "")
{
if (task == null) throw new ArgumentNullException(nameof(task));
// Allocate the async/await state machine only when needed for performance reasons.
// More info about the state machine: https://blogs.msdn.microsoft.com/seteplia/2017/11/30/dissecting-the-async-methods-in-c/?WT.mc_id=DT-MVP-5003978
// Pass params explicitly to async local function or it will allocate to pass them
static async Task ForgetAwaited(Task task, ILogger logger = null, string callingMethodName = "")
{
try
{
await task;
}
catch (TaskCanceledException tce)
{
// log a message if we were given a logger to use
logger?.LogError(tce, $"Fire and forget task was canceled for calling method: {callingMethodName}");
}
catch (Exception e)
{
// log a message if we were given a logger to use
logger?.LogError(e, $"Fire and forget task failed for calling method: {callingMethodName}");
}
}
// note: this code is inspired by a tweet from Ben Adams: https://twitter.com/ben_a_adams/status/1045060828700037125
// Only care about tasks that may fault (not completed) or are faulted,
// so fast-path for SuccessfullyCompleted and Canceled tasks.
if (!task.IsCanceled && (!task.IsCompleted || task.IsFaulted))
{
// use "_" (Discard operation) to remove the warning IDE0058: Because this call is not awaited, execution of the
// current method continues before the call is completed - https://docs.microsoft.com/en-us/dotnet/csharp/discards#a-standalone-discard
_ = ForgetAwaited(task, logger, callingMethodName);
}
}
}
}