如何启动异步方法而无需等待其完成?

49
有时候我需要启动一个很慢的异步任务,但我不在意它是否成功,并且需要在当前线程上继续工作。
例如,有时我需要发送电子邮件或短信,这需要很长时间。我需要尽快响应网络客户端,所以我不想使用await
我已经搜索了一些文章,有些建议我这样写:
// This method has to be async
public async Task<Response> SomeHTTPAction()
{
    // Some logic...
    // ...

    // Send an Email but don't care if it successfully sent.
    Task.Run(() =>  _emailService.SendEmailAsync());
    return MyRespond();
}

或者像这样:

// This method has to be async
public async Task<Response> SomeHTTPAction()
{
    // Some logic...
    // ...

    // Send an Email but don't care if it successfully sent.
    Task.Factory.StartNew(() =>  _emailService.SendEmailAsync());
    return MyRespond();
}

会出现一个警告,提示:在调用完成之前,请考虑将 'await' 操作符应用于调用的结果。

那如果我真的 await 了呢?在C#中,如何最好地“点火并忘记”,即调用异步方法而不等待其完成?


1
只是不要将函数标记为async,这样函数就不应该期望它的结果被等待? - Amadan
3
可能是 如何在 C# 中安全地调用异步方法而不使用 await 的重复内容。 - Tyler Roper
更好的方法是将这些操作作为进程运行,即发布到像rabbitmq这样的队列中,然后由订阅者接收这些消息,然后发送电子邮件/短信。 - JohanP
你目前找到了什么信息?快速谷歌搜索“C#异步火并忘记”会显示有很多相关资源可供使用。 - Alex McMillan
这不是一个_设计_问题吗?在确定操作是否完成之前返回MyRespond有什么意义呢? - user585968
由于您提到了Web客户端,如何在ASP.Net中运行后台任务 - Damien_The_Unbeliever
8个回答

46

1
这个答案是正确的。我只是因为它与MrMaavin的早期答案非常相似而进行了下投票。 - Theodor Zoulias

34

如果你真的只想发射并忘记,请不要调用使用 await。

// It is a good idea to add CancellationTokens
var asyncProcedure = SomeHTTPAction(cancellationToken).ConfigureAwait(false);

// Or If not simply do:
var asyncProcedure = SomeHTTPAction().ConfigureAwait(false);

如果您想稍后使用结果输出,那就会变得更加棘手。但如果确实是“fire and forget”,那么上面的方法应该可以工作。

取消令牌允许中断和取消过程。如果您正在使用取消令牌,则需要从检索直到调用方法处使用它(无止境的龟)。

我使用了ConfigureAwait(false)来防止死锁。 这里有更多信息。


编辑

请参阅第二个答案,其中使用'Task.Factory.StartNew'。我之前给出了这个答案。当时我没有意识到我当时所做的方式不能确保完成。


2
“我使用了 ConfigureAwait(false) 来防止死锁。”-- ConfigureAwait 方法用于配置 await。如果您不 await 它,那么它就完全没有效果。因为一开始就不存在死锁的风险,所以它不会防止任何死锁。 - Theodor Zoulias

10
如果你的函数需要使用async,那么你也可以使用一个过滤变量,不使用await。这在你有多个异步函数调用但不需要等待它们全部完成时也很有用。
public async function(){
    var tmp = await asyncfunction();
    ...
    _ = _httpClient.PutAsync(url, content);
    ...
}

6

正如评论中的Amadan所说,您需要从函数中删除async。然后它将停止向您发出警告。

// This method has to be async
public Response SomeHTTPAction()
{
     // Some logic...
     // ...
     // Send an Email but don't care if it successfully sent.
     Task.Factory.StartNew(() =>  _emailService.SendEmailAsync());
     return MyRespond();
}

Task.Factory.StartNew(() => _emailService.SendEmailAsync());确实可以在新线程上工作。


0

这完全取决于你的异步方法接受什么。通常它会接受一个“特殊”的类,该类还包含一个事件。您可以订阅回调方法到该事件并将其与方法一起传递。当完成时,将调用您的回调方法。

这种情况的一个示例(对于套接字)是:

    public void CreateSocket()
    {
        Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        SocketAsyncEventArgs sockAsync = new SocketAsyncEventArgs();
        sockAsync.Completed += SockAsync_Completed;

        s.ConnectAsync(sockAsync);
    }

    private void SockAsync_Completed(object sender, SocketAsyncEventArgs e)
    {
        //Do stuff with your callback object.
    }

这完全取决于你尝试调用的方法可以接受什么。我建议查看文档以获取更多相关帮助。


我必须承认,在阅读了你的回答之后,我意识到OP的问题并不是非常具体,而且有点倒霉的话可能会被误解。总的来说,你所写的是正确的 - 但不幸的是,你描述的并不是问题所问的那个“异步事物”。在dotnet apis等中有几种“异步模式”。你在这里提到的是基于事件/回调的异步api。还有经典的Begin/End方法对,几乎和它一样古老甚至更古老。但在这个问题中,他们问的是在2013年左右添加到Net4.5中的async/await模式。 - quetzalcoatl

-2

对我来说,Task.Run与async await的组合效果最佳,即使没有discard。我认为它在自己的上下文中运行,并且不会在父上下文终止时终止,例如在具有依赖注入的Azure函数中。

Task.Run(async () => await _emailService.SendEmailAsync());

-2

我很好奇为什么这个还没有被建议。

new Thread(() =>
{
    Thread.CurrentThread.IsBackground = true;
    //what ever code here...e.g.
    DoSomething();
    UpdateSomething();
}).Start();  

它只是启动一个单独的线程。


5
这将创建一个新线程并在该新线程上运行方法,这会相当昂贵/占用资源。这不是相关问题的答案。 - naltun
因为尽管它在某种程度上暗示了如何在不等待的情况下运行异步操作(如果你不关心或有重要原因等),但所提供的代码和解释中没有一个 'async' 或 'await' 关键字,并且它们确实会显著改变你构建线程主体的方式。 - quetzalcoatl

-3
我使用了user2866442的解决方案的一部分,但是我正在使用一个带有Lambda的Action来提供异步执行的内容。
Action<object, long> sendEmail = (var1, id) => 
{
    try
    {
        //Execute some code here
    }
    catch (Exception ex) 
    { 
        Log.ErrorException("sendEmail Error: " + ex.Message, ex); //Using NLog here.
    } 
    //Dont throw any errors here -it doesnt matter because this an async 
};

_ = System.Threading.Tasks.Task.Run(() => sendEmail(var1, id));  
//Run our sendMail code above without waiting for it to finish. 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接