消防并忘记异步任务与任务.运行

big*_*zhu 0 c# asp.net multithreading asynchronous async-await

在开发一些ASP.Net应用程序时,我看到一些空引用问题.例外情况如下:

Description: The process was terminated due to an unhandled exception.
Exception Info: System.NullReferenceException
Stack:
   at System.Threading.Tasks.AwaitTaskContinuation.<ThrowAsyncIfNecessary>b__1(System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
Run Code Online (Sandbox Code Playgroud)

在搜索了Google和SO之后,很可能是由于我的一些代码触发并以这种方式忘记了异步任务:

public interface IRepeatedTaskRunner
{
    Task Start();
    void Pause();
}

public class RepeatedTaskRunner : IRepeatedTaskRunner
{
    public async Task Start() //this is returning Task
    {
        ...
    }
}

public class RepeatedTaskRunnerManager{
    private IRepeatedTaskRunner _runner;
    public void Foo(){
        _runner.Start(); //this is not awaited.
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为它非常类似于这个问题"在asp.net mvc中消防并忘记异步方法",但并不完全相同,因为我的代码不在控制器上并接受请求.我的代码专门用于运行后台任务.

正如在上面的SO问题中一样,通过将代码更改为下面的问题修复了问题,但我只是想知道为什么以及与从任务返回的异常任务和任务返回的异常有什么区别:

public interface IRepeatedTaskRunner
{
    Task Start();
    void Pause();
}

public class RepeatedTaskRunner : IRepeatedTaskRunner
{
    public Task Start() // async is removed.
    {
        ...
    }
}

public class RepeatedTaskRunnerManager{
    private IRepeatedTaskRunner _runner;
    public void Foo(){
        Task.Run(() => _runner.Start()); //this is not waited.
    }
}
Run Code Online (Sandbox Code Playgroud)

从我的角度来看,这两个代码只创建了一个任务而忘了它.唯一的区别是第一个没有等待任务.这会导致行为上的差异吗?

我知道火灾和忘记模式的限制和不良影响,因为从另一个SO问题.

Ste*_*ary 14

我只是想知道为什么以及从Task.Run返回的fire-and-forgetting async Task和task有什么区别

不同之处在于是否AspNetSynchronizationContext捕获了"请求上下文"(包括当前文化,会话状态等内容).

直接调用时,Start在该请求上下文中运行,await默认情况下将捕获该上下文并在该上下文中恢复(我的博客上有更多信息).如果Start尝试在已经完成的请求的请求上下文上继续尝试,则这可能导致"有趣"的行为.

使用时Task.Run,Start在没有请求上下文的其他线程池线程上运行.因此,await不会捕获上下文并将在任何可用的线程池线程上恢复.

我知道你已经知道了这一点,但我必须重申Google员工:请记住,任何以这种方式排队的工作都不可靠.至少,如果你还没有使用4.5.2 ,你应该使用HostingEnvironment.QueueBackgroundWorkItem(或我的AspNetBackgroundTasks库).这些都非常相似; 他们会将后台工作排队到线程池(事实上,我的库BackgroundTaskManager.Run确实调用了Task.Run),但是这两个解决方案也将采取额外的步骤来注册 ASP.NET运行时的工作.这还不足以使背景在真正意义上的"可靠"工作,但它确实最大限度地减少了你失去工作的机会.