Prevent IIS from killing a Task before it ends

2019-02-06 14:04发布

I'm building a Logging library that stores everything on an Azure table. Writing to that table obviously takes a lot of time (never more than 1 sec, but it's still too much to make the user wait), so Log method returns a LogResult instance, here's the class

public class LogResult
{
    public string Id { get; set; }
    public Task LoggingTask { get; set; }

    public LogResult(string id, Task task)
    {
        Id = id;
        LoggingTask = task;
    }
}

And here is how the Log method finishes

return new LogResult(id, Task.Factory.StartNew(() => 
    DoLogInAzure(account, id, exception, request))
);

To give the caller the option of waiting for it to complete (if it's a console app, for instance). The issue I'm facing is that IIS shouldn't wait for it before returning the user the response... and if I don't wait for it, IIS doesn't always execute the task. The idea is to show the user a message "... If you contact us, be sure to mention your issue number, XXX" and don't make him wait until the log entry has been written.

Is there any way to force IIS to wait until the task finishes, even after it returned the response? I'm thinking I may need to code a Windows Service that takes the request asynchronously, but it looks like a lot of work just to add a log entry... specially if I can force IIS to wait for it.

Thanks for any ideas!

4条回答
我欲成王,谁敢阻挡
2楼-- · 2019-02-06 14:34

This post from Phil Hack talks about running background tasks in an ASP.NET application.

查看更多
beautiful°
3楼-- · 2019-02-06 14:39

The information is not enough but I suspect it may be related to GC and references if it works when you wait fo the task. For your purpose, a better way is to use ETW (EventProvider) and set the ActivityId for each request. Simply configure an ETW session can redirect all the messages to a file. You can show the ActivityId (a Guid) to the end user.

查看更多
【Aperson】
4楼-- · 2019-02-06 14:39

Sorry for not adding this as a comment, I don't have enough rep.

https://msdn.microsoft.com/en-us/library/system.web.hosting.iregisteredobject(v=vs.110).aspx

Applications can have only one instance of a registered type.

This seems to indicate that Gervasio Marchand's accepted answer is somewhat incorrect, as each call to his static helper method creates a new IISNotifier, which is an IRegisteredObject.

查看更多
手持菜刀,她持情操
5楼-- · 2019-02-06 14:47

Thanks to Damian Schenkelman and that blog post of Phil Haack, I figured out the problem and the solution. The problem is that IIS reuses the threads when it needs to handle new requests. And as it doesn't know that my task is doing some work, it reuses that thread (which makes sense). Then, I just have to notify IIS that I'm using that thread and that it can't be reused (so, it has to either reuse another thread, create a new one, or make it wait). I ended up using my own TaskFactory that handles the task creation, and automatically registers a notifier in IIS. For completeness, to help some other folk with the same issue as me, and to read another suggestions, here's what I've done

public class IISNotifier : IRegisteredObject
{
    public void Stop(bool immediate)
    {
        // do nothing, I only run tasks if I know that they won't
        // take more than a few seconds.
    }

    public void Started()
    {
        HostingEnvironment.RegisterObject(this);
    }

    public void Finished()
    {
        HostingEnvironment.UnregisterObject(this);
    }
}

And then

public class IISTaskFactory
{
    public static Task StartNew(Action act)
    {
        IISNotifier notif = new IISNotifier();
        notif.Started();
        return Task.Factory.StartNew(() => {
            act.Invoke();
            notif.Finished();
        });
    }
}

Now, when I want to start a the log task I just do

return new LogResult(id, IISTaskFactory.StartNew(() => 
    DoLogInAzure(account, id, exception, request))
);

You can see (and download the code) at https://github.com/gmc-dev/IISTask

查看更多
登录 后发表回答