任务后延续不工作的await [英] Task continuation not working after await

查看:257
本文介绍了任务后延续不工作的await的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到一个pretty奇怪的情况下,当任务执行后,是不是继续等待在IIS(不知道如果它涉及到IIS)。我使用转载Azure存储及以下控制器(全在github 溶液)这个问题:

 公共类HomeController的:控制器
{
    私有静态诠释_count;    公众的ActionResult指数()
    {
        RunRequest(); //我不想等待这个任务
        返回查看(_count);
    }    公共异步任务RunRequest()
    {
        CloudStorageAccount账户= CloudStorageAccount.DevelopmentStorageAccount;
        。VAR cloudTable = account.CreateCloudTableClient()GetTableReference(测试);        Interlocked.Increment(REF _count);
        等待Task.Factory.FromAsync<布尔>(cloudTable.BeginCreateIfNotExists,cloudTable.EndCreateIfNotExists,NULL);        Trace.WriteLine(任务的这部分后的await永远不会执行);
        Interlocked.Decrement(REF _count);
    }
}

我希望 _count 的值总是1时(鉴于渲染),但如果你按F5键几次,你会看到 _count 每次刷新后递增。这意味着,延续不叫出于某种原因。

事实上,我也骗了一下,我注意到,连续被调用一次,当被称为首次首页。所有其他的F5的不递减计数器。

如果我改变方法是异步:

 公共异步任务<&的ActionResult GT;指数()
    {
        等待RunRequest(); //我不想等待这个任务
        返回查看(_count);
    }

一切开始工作正常,只是我不想让客户在等待我的异步操作完成。

所以我的问题是:我想明白为什么会这样,什么是运行射后不理pferably工作,$ P $没有跨越新线程一致的方式


解决方案

  

什么是运行射后不理的一贯工作方式


ASP.NET不是设计用来发射后不管工作;它的目的是服务于HTTP请求。当生成一个HTTP响应(当你的行动回报),即请求/响应周期完成。

注意,ASP.NET会随意记下你的AppDomain有没有活动的请求的任何时间。这是对共享主机正常完成闲置超时后,或者当您的AppDomain已经有一定数量的垃圾收集,或每29个小时才毫无理由可言。

所以,你真的不希望射后不理 - 要生成的响应,但是的的有ASP.NET忘掉它。的简单的解决方案 ConfigureAwait(假)将引起大家忘掉它,这意味着一旦在一个蓝色的月亮你继续下去,一下就丢失。

我有一篇博客文章中说进入对这个问题的详细信息。总之,你想要的生成的响应之前的录制到一个持久层来完成(像天青表)的工作。这是理想的解决方案。

如果你不打算做了理想的解决方案,那么你将<一个href=\"http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx\"相对=nofollow>居住危险。有code在我的博客文章,将注册工作 s的ASP.NET运行时,这样就可以提前返回响应,但ASP.NET通知你又不是的真正的没有完成。这将prevent ASP.NET采取了你的网站,而你有出色的工作,但它不会保护你免受像一个硬盘驱动器崩溃或某人更根本的失败绊倒你的服务器的电源线。

在我的博客文章的code如下重复;它在我的 AsyncEx库 AsyncCountdownEvent >:

 使用系统;
使用的System.Threading;
使用System.Threading.Tasks;
使用System.Web.Hosting;
使用Nito.AsyncEx;///&LT;总结&gt;
///,跟踪后台操作,并通知ASP.NET,他们仍在进行中A型。
///&LT; /总结&gt;
公共密封类BackgroundTaskManager:IRegisteredObject
{
    ///&LT;总结&gt;
    ///这是当ASP.NET正在关闭应用程序域设置一个取消标记。
    ///&LT; /总结&gt;
    私人只读CancellationTokenSource关机;    ///&LT;总结&gt;
    ///,每当一个任务被注册时间递增,每次完成时间递减的倒计时活动。当它达到零,我们准备关闭应用程序域。
    ///&LT; /总结&gt;
    私人只读AsyncCountdownEvent计数;    ///&LT;总结&gt;
    ///后&LT的完成任务;见CREF =计数/&GT;达到零并且对象已取消注册。
    ///&LT; /总结&gt;
    私人只读任务完成的;    私人BackgroundTaskManager()
    {
        //从1开始计数和减少它时,ASP.NET通知我们我们正在关闭。
        关机=新CancellationTokenSource();
        计数=新AsyncCountdownEvent(1);
        shutdown.Token.Register(()=&GT; count.Signal(),useSynchronizationContext:假);        //注册的对象,当计数到达零注销它。
        HostingEnvironment.RegisterObject(本);
        做= count.WaitAsync()ContinueWith(_ =&GT; HostingEnvironment.UnregisterObject(本),TaskContinuationOptions.ExecuteSynchronously)。
    }    无效IRegisteredObject.Stop(布尔即时)
    {
        shutdown.Cancel();
        如果(立即)
            done.Wait();
    }    ///&LT;总结&gt;
    ///与注册ASP.NET运行时的任务。
    ///&LT; /总结&gt;
    ///&LT; PARAM NAME =任务&gt;到注册的任务&LT; /参数&GT;
    私人无效注册(任务任务)
    {
        count.AddCount();
        task.ContinueWith(_ =&GT; count.Signal(),TaskContinuationOptions.ExecuteSynchronously);
    }    ///&LT;总结&gt;
    ///此应用程序域的后台任务管理器。
    ///&LT; /总结&gt;
    私人静态只读BackgroundTaskManager实例=新BackgroundTaskManager();    ///&LT;总结&gt;
    ///获取当ASP.NET正在关闭应用程序域设置取消标记。
    ///&LT; /总结&gt;
    公共静态的CancellationToken关机{{返回instance.shutdown.Token; }}    ///&LT;总结&gt;
    ///执行一个&LT; C&GT;异步&LT; / c取代;后台操作,使用ASP.NET注册它。
    ///&LT; /总结&gt;
    ///&LT; PARAM NAME =运行&gt;在后台操作&LT; /参数&GT;
    公共静态无效运行(Func键&LT;任务&GT;操​​作)
    {
        instance.Register(Task.Run(操作));
    }    ///&LT;总结&gt;
    ///执行后台操作,使用ASP.NET注册它。
    ///&LT; /总结&gt;
    ///&LT; PARAM NAME =运行&gt;在后台操作&LT; /参数&GT;
    公共静态无效运行(动作操作)
    {
        instance.Register(Task.Run(操作));
    }
}

它可以用来像这样异步或同步code:

  BackgroundTaskManager.Run(()=&GT;
{
    //同步例子
    Thread.sleep代码(20000);
});
BackgroundTaskManager.Run(异步()=&GT;
{
    //例如异步
    等待Task.Delay(20000);
});

I've encountered a pretty strange case when task execution is not continued after await in IIS (not sure if it's related to IIS). I reproduced this issue using Azure Storage and following controller (full solution on github):

public class HomeController : Controller
{
    private static int _count;

    public ActionResult Index()
    {
        RunRequest(); //I don't want to wait on this task
        return View(_count);
    }

    public async Task RunRequest()
    {
        CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
        var cloudTable = account.CreateCloudTableClient().GetTableReference("test");

        Interlocked.Increment(ref _count);
        await Task.Factory.FromAsync<bool>(cloudTable.BeginCreateIfNotExists, cloudTable.EndCreateIfNotExists, null);

        Trace.WriteLine("This part of task after await is never executed");
        Interlocked.Decrement(ref _count);
    }
}

I would expect the value of _count to be always 1 (when rendered in view), but if you hit F5 several time you'll see that _count is incrementing after each refresh. That means that continuation is not called for some reason.

In fact I've lied a bit, I've noticed that continuation is called once, when Index is called for the first time. All further F5's don't decrement the counter.

If I change the method to be async:

    public async Task<ActionResult> Index()
    {
        await RunRequest(); //I don't want to wait on this task
        return View(_count);
    }

everything starts working as expected except that I don't want to keep client waiting for my asynchronous operation to finish.

So my question is: I would like to understand why this happens, and what is the consistent way to run "fire and forget" work, preferably without spanning new threads.

解决方案

what is the consistent way to run "fire and forget" work

ASP.NET was not designed for fire-and-forget work; it was designed to serve HTTP requests. When an HTTP response is generated (when your action returns), that request/response cycle is complete.

Note that ASP.NET will feel free to take down your AppDomain any time that there are no active requests. This is normally done on shared hosts after an inactivity timeout, or when your AppDomain has had a certain number of garbage collections, or every 29 hours just for no reason at all.

So you don't really want "fire and forget" - you want to produce the response but not have ASP.NET forget about it. The simple solution of ConfigureAwait(false) will cause everyone to forget about it, which means that once in a blue moon your continuation could just get "lost".

I have a blog post that goes into more detail on this subject. In short, you want to record the work to be done in a persistent layer (like an Azure table) before your response is generated. That's the ideal solution.

If you aren't going to do the ideal solution, then you're going to live dangerously. There is code in my blog post that will register Tasks with the ASP.NET runtime, so that you can return a response early but notify ASP.NET that you're not really done yet. This will prevent ASP.NET from taking down your site while you have outstanding work, but it will not protect you against more fundamental failures like a hard drive crash or someone tripping over the power cord of your server.

The code in my blog post is duplicated below; it depends on the AsyncCountdownEvent in my AsyncEx library:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Hosting;
using Nito.AsyncEx;

/// <summary>
/// A type that tracks background operations and notifies ASP.NET that they are still in progress.
/// </summary>
public sealed class BackgroundTaskManager : IRegisteredObject
{
    /// <summary>
    /// A cancellation token that is set when ASP.NET is shutting down the app domain.
    /// </summary>
    private readonly CancellationTokenSource shutdown;

    /// <summary>
    /// A countdown event that is incremented each time a task is registered and decremented each time it completes. When it reaches zero, we are ready to shut down the app domain. 
    /// </summary>
    private readonly AsyncCountdownEvent count;

    /// <summary>
    /// A task that completes after <see cref="count"/> reaches zero and the object has been unregistered.
    /// </summary>
    private readonly Task done;

    private BackgroundTaskManager()
    {
        // Start the count at 1 and decrement it when ASP.NET notifies us we're shutting down.
        shutdown = new CancellationTokenSource();
        count = new AsyncCountdownEvent(1);
        shutdown.Token.Register(() => count.Signal(), useSynchronizationContext: false);

        // Register the object and unregister it when the count reaches zero.
        HostingEnvironment.RegisterObject(this);
        done = count.WaitAsync().ContinueWith(_ => HostingEnvironment.UnregisterObject(this), TaskContinuationOptions.ExecuteSynchronously);
    }

    void IRegisteredObject.Stop(bool immediate)
    {
        shutdown.Cancel();
        if (immediate)
            done.Wait();
    }

    /// <summary>
    /// Registers a task with the ASP.NET runtime.
    /// </summary>
    /// <param name="task">The task to register.</param>
    private void Register(Task task)
    {
        count.AddCount();
        task.ContinueWith(_ => count.Signal(), TaskContinuationOptions.ExecuteSynchronously);
    }

    /// <summary>
    /// The background task manager for this app domain.
    /// </summary>
    private static readonly BackgroundTaskManager instance = new BackgroundTaskManager();

    /// <summary>
    /// Gets a cancellation token that is set when ASP.NET is shutting down the app domain.
    /// </summary>
    public static CancellationToken Shutdown { get { return instance.shutdown.Token; } }

    /// <summary>
    /// Executes an <c>async</c> background operation, registering it with ASP.NET.
    /// </summary>
    /// <param name="operation">The background operation.</param>
    public static void Run(Func<Task> operation)
    {
        instance.Register(Task.Run(operation));
    }

    /// <summary>
    /// Executes a background operation, registering it with ASP.NET.
    /// </summary>
    /// <param name="operation">The background operation.</param>
    public static void Run(Action operation)
    {
        instance.Register(Task.Run(operation));
    }
}

It can be used like this for async or synchronous code:

BackgroundTaskManager.Run(() =>
{
    // Synchronous example
    Thread.Sleep(20000);
});
BackgroundTaskManager.Run(async () =>
{
    // Asynchronous example
    await Task.Delay(20000);
});

这篇关于任务后延续不工作的await的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆