簡體   English   中英

觸發並忘記異步Task與Task.Run

[英]Fire and forget async Task vs Task.Run

開發某些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()

在通過Google和SO搜索之后,很可能是由於我的某些代碼觸發並以這種方式忘記了異步Task造成的:

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.
    }
}

我認為它與這個問題“在asp.net mvc中觸發並忘記異步方法”非常相似,但是並不完全相同,因為我的代碼不在控制器上並且不能接受請求。 我的代碼專用於運行后台任務。

與上述SO問題中的問題相同,通過將代碼更改為以下內容可以解決此問題,但我只想知道為什么與即發即棄異步Task和從Task.Run返回的任務有何區別?

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.
    }
}

從我的角度來看,兩個代碼都只創建一個任務,而忽略了它。 唯一的區別是第一個任務沒有等待。 那會導致行為上的差異嗎?

我知道火災和遺忘模式的局限性和不良影響,就像另一個SO問題一樣。

我只想知道為什么解雇異步任務和從Task.Run返回的任務有何區別?

區別在於是否捕獲了AspNetSynchronizationContext ,即“請求上下文”(包括諸如當前區域性,會話狀態等)。

直接調用時, Start在該請求上下文中運行,默認情況下, await將捕獲該上下文並在該上下文上繼續(更多信息,請參見我的博客 )。 如果,例如, Start嘗試針對已完成的請求在請求上下文上恢復,則會導致“有趣的”行為。

使用Task.RunStart在沒有請求上下文的其他線程池線程上運行。 因此, await將不會捕獲上下文,並且將在任何可用的線程池線程上繼續。

我知道您已經知道這一點,但是我必須向Google員工重申:請記住,以這種方式排隊的任何工作都不可靠。 至少,您應該使用HostingEnvironment.QueueBackgroundWorkItem (如果尚未使用4.5.2,請使用我的AspNetBackgroundTasks庫 )。 這些都非常相似。 它們會將后台工作排隊到線程池中(實際上,我的庫的BackgroundTaskManager.Run確實調用Task.Run ),但是這兩種解決方案都將采取額外的步驟在ASP.NET運行時中注冊該工作。 僅憑其真實含義,這還不足以使后台工作“可靠”,但這確實使您丟失工作的機會降到最低

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM