I have a long running process that is called via a Nancy Module Get method. Rather than have the browser hang while the process runs I would like this call to be asynchronous, in other words I want the Get method to return right away and leave the long running process to do its thing. I could then check the status of the process at regular intervals and act accordingly.
I am new to the async / await features of C#5 but I feel sure these are meant for just this kind of task. The code below shows my attempt. I have put in some Logging that demonstrates that it is not running asynchronously as expected. Instead the long running process blocks the Get method and so the browser hangs.
Module
public class TestModule : NancyModule
{
public TestModule(ITestService testService)
{
Get["/longprocess"] = _ =>
{
Log.Write("Module : Start");
testService.LongProcess();
Log.Write("Module : Finish");
return HttpStatusCode.OK;
};
}
}
Service
public interface ITestService
{
Task<bool> LongProcess();
}
public class TestService : ITestService
{
public async Task<bool> LongProcess()
{
await LongProcessAsynch();
return true;
}
private Task<bool> LongProcessAsynch()
{
Log.Write("LongProcess : Start");
Thread.Sleep(5000);
//Task.Delay(5000); <- Has same effect
Log.Write("LongProcess : Finish");
return true.AsTask();
}
}
Extension Method
public static Task<T> AsTask<T>(this T candidate)
{
var source = new TaskCompletionSource<T>();
source.SetResult(candidate);
return source.Task;
}
Log Output
14/06/2012 19:22:29 : Module : Start
14/06/2012 19:22:29 : LongProcess : Start
14/06/2012 19:22:34 : LongProcess : Finish
14/06/2012 19:22:34 : Module : Finish
You can see from the Log Output above that the LongProcess() is blocking the return of the Get module. If the task was running asynchronously I would expect the log to look something like this:
Expected Log
Module : Start
LongProcess : Start
Module : Finish
LongProcess : Finish
I think what is actually required is to put the await in the NancyModule Get method. Something like the code below perhaps, but I cannot do this because I cannot mark the module constructor as asnyc and so I cannot use await within the Get method (or so I currently believe)
Get["/longprocess"] = _ =>
{
Log.Write("Module : Start");
await testService.LongProcess();
Log.Write("Module : Finish");
return HttpStatusCode.OK;
};
Thanks for any help, examples or pointers to resources.
Edit 1
More research reveals that asp.net seems to actively prevent async calls by default. Something to do with the Session and the way it processes non UI threads I believe. So I have added the following to my web.config (see below)
- UseTaskFriendlySynchronizationContext
- enableSessionState="false"
Unfortunately it does not make any difference and my async / await call to LongProcess() is still blocking.
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
<add key="webPages:Enabled" value="false" />
</appSettings>
<system.web>
<httpRuntime targetFramework="4.5"/>
<pages controlRenderingCompatibilityVersion="4.0" enableSessionState="false" />
... more config
</configuration>
testService.LongProcess()is synchronous but this then calls theLongProcessAsynch()method using theawaitkeyword and so this should be asynchronous. Do you think this is correct?