Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my async ASP.NET Web API controller blocking the main thread?

I have an ASP.NET Web API controller that I would have thought would operate asynchronously. The controller is designed to sleep 20 seconds for the first request, but service any subsequent requests immediately. So my anticipated timeline would be something like:

  1. Raise request 1.
  2. Raise request 2.
  3. Raise request 3.
  4. Request 2 returns.
  5. Request 3 returns.
  6. Wait ~20 seconds.
  7. Request 1 returns.

Instead, no requests return until request 1 is finished.

I can confirm (based on the debug outputs), that the entry thread and sleepy thread id are different. I've intentionally used TaskCreationOptions.LongRunning to force the sleep onto a separate thread, but still the application refuses to service any new requests until that sleep has finished.

Am I missing something fundamental about how async Web API controllers really work?


public class ValuesController : ApiController
{
    private static bool _firstTime = true;

    public async Task<string> Get()
    {
        Debug.WriteLine("Entry thread id: {0}. Sync: {1}",
            Thread.CurrentThread.ManagedThreadId,
            SynchronizationContext.Current);
        await LongWaitAsync();
        return "FOOBAR";
    }

    private Task LongWaitAsync()
    {
        return Task.Factory.StartNew(() =>
            {
                if (_firstTime)
                {
                    _firstTime = false;
                    Debug.WriteLine("Sleepy thread id: {0}. Sync: {1}",
                        Thread.CurrentThread.ManagedThreadId,
                        SynchronizationContext.Current);
                    Thread.Sleep(20000);
                    Debug.WriteLine("Finished sleeping");
                }
            },
            CancellationToken.None,
            TaskCreationOptions.LongRunning,
            TaskScheduler.Default);
    }
}
like image 961
Snixtor Avatar asked Feb 21 '13 07:02

Snixtor


1 Answers

This actually has nothing to do with the server, and everything to do with the client. Both Chrome and Firefox don't appear to want to send what they deem a "duplicate" request until the first one has its response. A separate "private" session of either browser will return from the second request immediately. Internet Explorer 9 doesn't seem to exhibit this behaviour.

To isolate from client implementations, I put together the following client.

class Program
{
    static void Main(string[] args)
    {
        var t1 = Task.Run(() => FetchData(1));
        var t2 = Task.Run(() => FetchData(2));
        var t3 = Task.Run(() => FetchData(3));

        var index = Task.WaitAny(t1, t2, t3);
        Console.WriteLine("Task {0} finished first", index + 1);

        Task.WaitAll(t1, t2, t3);
        Console.WriteLine("All tasks have finished");

        Console.WriteLine("Press any key");
        Console.ReadKey(true);
    }

    static void FetchData(int clientNumber)
    {
        var client = new WebClient();
        string data = client.DownloadString("http://localhost:61852/api/values");
        Console.WriteLine("Client {0} got data: {1}", clientNumber, data);
    }
}

It's output goes:

  1. Client 2 got data: "FOOBAR" (within milliseconds of startup)
  2. Client 3 got data: "FOOBAR"
  3. Task 2 finished first
  4. (long wait here)
  5. Client 1 got data: "FOOBAR"
  6. All tasks have finished
like image 105
Snixtor Avatar answered Oct 23 '22 07:10

Snixtor