当父任务返回时,在后台运行一个异步任务的C#实现

7
我正在编写一个MVC5应用程序,其中有一个控制器和一个名为Method1的函数。
我有一个第三方函数是async Task,假设它叫DoMethodAsync(它本质上是进行POST调用)。
我想在从Method1返回之前立即调用它,但我并不真正关心异步任务的结果,如果失败了,那就失败了。如何快速启动它并在后台执行以使其不阻塞控制器响应其他请求?
如何确保Method1返回后才执行DoMethodAsync

你是想在Method1返回之前还是之后调用异步函数?它是一个异步函数...所以无论你在哪里调用它,只要不使用await,它就不会阻塞控制器。 - Jonathan Carroll
3
不要等待DoMethodAsync,这会生成一个编译器警告,但你可以忽略它,因为它正在执行你想要的操作。 - CodingGorilla
@codinggorilla 哇,真的吗?我可以做到吗? @JonathanCarroll 我想让 DoMethodAsync 在我返回后运行。但由于 Method1 是控制器唯一可以访问的函数,所以我必须在它内部创建 DoMethodAsync,就在返回之前。 - WindowsMaker
4个回答

15

如果异步任务失败了,我并不在意它的结果,失败就失败吧。

真的吗?只是问问,因为这是非常罕见的用例。

如何执行“发射并忘记”操作,并使其在后台执行,以便它不会阻塞控制器以响应其他请求?

我有一篇博客文章介绍了各种在ASP.NET上实现“发射并忘记”任务的方法。

您不希望仅仅调用方法并忽略返回的任务,因为该方法将继承启动请求的请求上下文。然后,在请求完成后,请求上下文将被释放,该方法可能会处于一个“有趣”的位置 - 有些东西可以工作,但有些东西则不能。

因此,最简单有效的解决方案是将其包装在 Task.Run中:

var _ignored = Task.Run(() => DoMethodAsync());

请注意,ASP.NET 完全不知道这个 "额外操作"。如果你想让 DoMethodAsync 有更大的机会真正执行完成,那么你需要将其注册到 ASP.NET 运行时中,可以使用 HostingEnvironment.QueueBackgroundWorkItem (4.5.2+) 或者 我自己的 BackgroundTaskManager.Run (4.5.0/4.5.1)。

还要注意,没有一个选项可以保证 DoMethodAsync 能够完整地运行;它们只是尽力而为。对于一个真正可靠的系统,你需要一个适当的分布式架构,即具备独立处理器的可靠存储。


感谢您详细的回答。对于第一个问题,我确实关心,但我宁愿先让它工作起来。如果更改实现方式,那么我肯定会选择允许我捕获失败的方法。使用“Run”不会阻塞调用者吗?您所说的“没有保证完成运行”是什么意思?它们会在一半停止还是根本不执行?我确实考虑过Hangfire,但首选轻量级解决方案。 - WindowsMaker
@zaftcoAgeiha:我的意思是ASP.NET是围绕着响应请求而设计的。它不是为了在请求之外做工作而设计的。当没有请求时,IIS可以关闭它(并且会定期回收它以保持清洁)。如果此时有一个Task.Run任务正在运行,它将被无情地终止。 - Stephen Cleary
@Stephen Cleary 我看了很多教程,只有这个答案帮了我。现在我的代码可以在不影响用户工作体验的情况下运行。非常感谢你。 - NMathur
你不认为“基于请求启动后台工作,但不想让请求等待工作完成”是一个极其罕见的使用情况吗?你认为忘记密码机制是一个极其罕见的使用情况吗?你认为记录用户活动但不想让请求等待记录操作完成是极其罕见的吗? - JounceCracklePop
@CarlLeth:我说过,不关心操作是否失败(或者说,是否完全完成)是非常罕见的。忘记密码机制忽略所有发送电子邮件错误也是非常罕见的。忘记密码的常见解决方案是一个适当的分布式架构(队列+服务);而日志记录的常见解决方案是在内存中排队,在关闭时刷新。异步的fire-and-forget 应该是非常罕见的。 - Stephen Cleary

1
我知道这已经过时了,但是经过几个小时的搜索和尝试不同的方法后,我找到了这个线程。将这里的一些信息与其他资源结合起来,我能够实现我想要的东西,我相信这也是OP想要的东西。
我想调用像/API/Start这样的URL,并希望该URL立即返回一个视图,上面显示“感谢您的启动,请稍后查看”。但是它需要简单地启动一个后台作业。
这是使用MVC 5完成的,所以不确定是否有区别,但是...
附注:要查看此操作,请使用2个URL /Default/Index <-- 将报告约5秒钟 /Default/IndexAsync <-- 将报告约0秒钟(嗯,有点 ...你会看到)
public class DefaultController : Controller
{

    // GET: Sync
    public ActionResult Index()
    {
        System.Diagnostics.Stopwatch myStopwatch = new System.Diagnostics.Stopwatch();
        myStopwatch.Start();
        DoTask();
        myStopwatch.Stop();

        ViewBag.Message = ($"Process took {myStopwatch.Elapsed.TotalSeconds.ToString()}");
        return View("Index");
    }

    // GET: Async
    public ActionResult IndexAsync()
    {
        System.Diagnostics.Stopwatch myStopwatch = new System.Diagnostics.Stopwatch();
        myStopwatch.Start();
        Task.Factory.StartNew(DoTaskAsync);
        myStopwatch.Stop();

        ViewBag.Message = ($"Process took {myStopwatch.Elapsed.TotalSeconds.ToString()}");
        return View("Index");
    }

    // "the Sync Task"
    private void DoTask()
    {
        System.Diagnostics.Debug.Print($"DoTask Started at {DateTime.Now.ToString()}");
        System.Threading.Thread.Sleep(5000);
        System.Diagnostics.Debug.Print($"DoTask Finished at {DateTime.Now.ToString()}");
    }

    // "the Async Task"
    private async Task DoTaskAsync()
    {
        System.Diagnostics.Debug.Print($"DoTaskAsync Started at {DateTime.Now.ToString()}");
        System.Threading.Thread.Sleep(5000);
        System.Diagnostics.Debug.Print($"DoTaskAsync Finished at {DateTime.Now.ToString()}");
    }      

}

0
public ActionResult Index()
{
    DoMethodAsync(); // if you do not await and simple call this task it will run in backround as you want
    return View();
}

async Task DoMethodAsync()
{
    // calculate something
}

0
调用异步方法意味着调用方法(您的代码)在执行异步方法时不会被阻塞,至少默认情况下是这样的(您可以通过使用“await”关键字或调用任务的一个阻塞方法如“Result”或“Wait”等来使您的代码阻塞)。
这意味着如果您只是调用该方法,该方法将异步运行,您的代码将不会等待它结束。我认为这正是您想要的。

太棒了!但是如果我想要确保DoMethodAsyncMethod1返回后执行怎么办呢?但是我无法访问Method1之外的东西。有没有比在DoMethodAsync上设置时间更好的方法? - WindowsMaker
@zaftcoAgeiha,你想要做的事情具体目的是什么? - m1o2
只要它是非阻塞异步任务,那么它有什么区别呢? - Gurpreet

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