Over a million developers have joined DZone.

Deadlocking with the TPL, how to

· Database Zone

To stay on top of the changing nature of the data connectivity world and to help enterprises navigate these changes, download this whitepaper from Progress Data Direct that explores the results of the 2016 Data Connectivity Outlook survey.

As I mentioned, I run into a very nasty issue with the TPL. I am not sure if it is me doing things wrong, or an actual issue.

Let us look at the code, shall we?

We start with a very simple code:

    public class AsyncEvent
    {
        private volatile TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
        
        public Task WaitAsync()
        {
            return tcs.Task;
        }
     
       public void PulseAll()
       {
           var taskCompletionSource = tcs;
           tcs = new TaskCompletionSource<object>();
           taskCompletionSource.SetResult(null);
       }
   }

This is effectively an auto reset event. All the waiters will be released when the PulseAll it called. Then we have this runner, which just execute work:

    public class Runner : IDisposable
    {
        private readonly ConcurrentQueue<TaskCompletionSource<object>> items =
            new ConcurrentQueue<TaskCompletionSource<object>>();
        private readonly Task<Task> _bg;
        private readonly AsyncEvent _event = new AsyncEvent();
        private volatile bool _done;
     
        public Runner()
       {
           _bg = Task.Factory.StartNew(() => Background());
       }
    
       private async Task Background()
       {
           while (_done == false)
           {
               TaskCompletionSource<object> result;
               if (items.TryDequeue(out result) == false)
               {
                   await _event.WaitAsync();
                   continue;
               }
    
               //work here, note that we do NOT use await!
    
               result.SetResult(null);
           }
       }
    
       public Task AddWork()
       {
           var tcs = new TaskCompletionSource<object>();
           items.Enqueue(tcs);
    
           _event.PulseAll();
    
           return tcs.Task;
       }
    
       public void Dispose()
       {
           _done = false;
           _event.PulseAll();
           _bg.Wait();
       }
   }

And finally, the code that causes the problem:

    public static async Task Run()
    {
        using (var runner = new Runner())
        {
            await runner.AddWork();
        }
    }

So far, it is all pretty innocent, I think you would agree. But this cause hangs with a dead lock. Here is why:

image

Because tasks can share threads, we are in the Background task thread, and we are trying to wait on that background task completion.

Result, deadlock.

If I add:

    await Task.Yield();

Because that forces this method to be completed in another thread, but that looks more like something that you add after you discover the bug, to be honest.















Turn Data Into a Powerful Asset, Not an Obstacle with Democratize Your Data, a Progress Data Direct whitepaper that explains how to provide data access for your users anywhere, anytime and from any source.

Topics:

Published at DZone with permission of Ayende Rahien, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}