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!
This post from Phil Hack talks about running background tasks in an ASP.NET application.
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
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.
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.