7

I am writing a library that is consuming a resource and for whatever reason the API was designed in a way that events will be raised on different threads but calls of the API has to be done on the main thread.

Let's say the API that I am trying to consume is defined as (I am going to omit event definitions):

public sealed class DodgyService
{
    public void MethodThatHasToBeCalledOnTheMainThread() { ... }
}

To consume this API I have added a service on my library called Service (Yup, very original name) that will create a new task (that will run on the main thread as I am specifying a TaskScheduler that has been created from the SynchronizationContext).

Here is my implementation:

public class Service
{
  private readonly TaskFactory _taskFactory;
  private readonly TaskScheduler _mainThreadScheduler;

  public Service(TaskFactory taskFactory, TaskScheduler mainThreadScheduler)
  {
      _taskFactory = taskFactory;
      _mainThreadScheduler = mainThreadScheduler;
  }

  // Assume this method can be called from any thread.
  // In this sample is called by the main thread but most of the time
  // the caller will be running on a background thread.
  public Task ExecuteAsync(string taskName)
  {
      return _taskFactory.StartNew(
          () => ReallyLongCallThatForWhateverStupidReasonHasToBeCalledOnMainThread(taskName),
          new CancellationToken(false), TaskCreationOptions.None, _mainThreadScheduler)
          .ContinueWith(task => Trace.TraceInformation("ExecuteAsync has completed on \"{0}\"...", taskName));
  }

  private void ReallyLongCallThatForWhateverStupidReasonHasToBeCalledOnMainThread(string taskName)
  {
      Trace.TraceInformation("Starting \"{0}\" really long call...", taskName);
      new DodgyService().MethodThatHasToBeCalledOnTheMainThread();
      Trace.TraceInformation("Finished \"{0}\" really long call...", taskName);
  }

}

Now, if I perform the call of my service (on the main thread) and try to wait on the main thread the application enters a deadlock as the main thread will be waiting for the tasks that has been scheduled to execute on the main thread.

How do I marshall these calls onto the main thread without blocking the entire process?

At some point I thought on performing the detection of the main thread before creating the new task but I don't want to hack this.

For anybody interested, I got a gist here with the code and a WPF app that exhibits the issue.

On btw, the library has to be written on .net framework 4.0

Edit! I solved my issue following the advice provided by Scott Chamberlain as provided here

Community
  • 1
  • 1
rodrigoelp
  • 2,550
  • 1
  • 19
  • 29
  • You are "Waiting" on the list of tasks not "Awaiting" in your example code in your gist, there is ***very*** important differences between the two. – Scott Chamberlain Nov 11 '13 at 23:00
  • You are right Scott... It was a typo. I have corrected it. – rodrigoelp Nov 12 '13 at 11:26
  • 1
    Is it that the API must be on the ***main*** thread or would it be ok for the API to run on a 2nd STA Message pumped thread? (see [this answer](http://stackoverflow.com/questions/4269800/webbrowser-control-in-a-new-thread/4271581#4271581) for a example of usage) – Scott Chamberlain Nov 12 '13 at 14:34
  • The API I am wrapping force me to call it on the main thread (regardless to what my app/service/whatever will consume it. – rodrigoelp Nov 12 '13 at 20:54
  • 1
    My question is what constitutes a "main thread" most things that requires to be run on "*the main*" thread are actually saying they require to be run on "*a STA Windows Message pumped that the object was initially created on*" thread. I am asking if starting a 2nd message pump in your application (like in the linked answer) would work for you. – Scott Chamberlain Nov 12 '13 at 20:56
  • Hi Scott, you were right. After playing with the code a bit more my issue was resolved by creating the second message pump and dispatch the calls in there. – rodrigoelp Nov 13 '13 at 03:37
  • I updated my answer to include the option of having a 2nd main thread. (Be sure to upvote Hans too, the idea is from one of his old answers from 3 years ago) – Scott Chamberlain Nov 13 '13 at 14:51

3 Answers3

8

as the main thread will be waiting for the tasks

That's a guaranteed deadlock. A task cannot execute on the main thread until it is idle, running the dispatcher loop (aka pumping the message loop). It is that dispatcher loop that implements the magic of getting code to run on a specific thread. The main thread however won't be idle, it is "waiting for the tasks". So the task cannot complete because the main thread won't go idle, the main thread cannot go idle because the task won't complete. Deadlock city.

You must rewrite the code so your main thread won't wait. Move whatever code that appears after the wait call to another task that runs on the main thread, just like that ReallyLongCall().

Do note that you don't seem to get any mileage at all from using tasks, your snippet suggests that none of the code that matters runs on a worker thread. So you might as well call it directly, solves the problem as well.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • That's right Hans, I know that is going to cause a deadlock if Task.Wait is performed on the task returned by the service. As I was pointing out on the statement after the last code snippet: How can I write my code in the Service class so the consumers of my library don't cause a deadlock? There is nothing worst than an API with serious side effects and driving the application to a deadlock is not desire at all. My question stands the same... How do I write my class in a way that marshalls the call to the main thread regardless of how the consumer implements their side? – rodrigoelp Nov 12 '13 at 11:42
  • It is *very* unclear how client code that uses your class could ever cause this problem. Surely the task is a private implementation detail of your class so there's just no way that client code could ever call its Wait() method. Raise an event that tells it your task is completed. Favor C# v5 support for async/await to make it easy for the client code to deal with the asynchronicity of it. And of course only ever use a task when you actually need one. – Hans Passant Nov 12 '13 at 12:54
  • I understand. Is it better then to expose my Service as `class Service { void ExecuteAsync(Action onComplete); }` than `class Service { Task ExecuteAsync(); }`? I was not trying to use tasks just for the sake of using it. I thought I could remove the usage of my `SynchronizationContext.Post(Action)` and favour tasks over (as my wrapper does plenty of background work). Thanks – rodrigoelp Nov 12 '13 at 21:02
2

From your example program:

  private void HandleClosed(object sender, EventArgs e)
  {
      var list = new[]
      {
          _service.ExecuteAsync("first task"),
          _service.ExecuteAsync("second task"),
          _service.ExecuteAsync("third task")
      };

      //uncommenting this line blocks all three previous activities as expected
      //as it drives the current main thread to wait for other tasks waiting to be executed by the main thread.

      //Task.WaitAll(list);
  }

Task.WaitAll is a blocking call, you can't perform blocking calls on the main thread or you will cause deadlocks. What you can do (if you are using Visual Studio 2012 or newer) is use the NuGet package Microsoft.Bcl.Async which gives async/await support to .Net 4.0.

After adding the package change the code to

private async void HandleClosed(object sender, EventArgs e)
{
    var list = new[]
  {
      _service.ExecuteAsync("first task"),
      _service.ExecuteAsync("second task"),
      _service.ExecuteAsync("third task")
  };

    //uncommenting this line blocks all three previous activities as expected
    //as it drives the current main thread to wait for other tasks waiting to be executed by the main thread.

    await TaskEx.WhenAll(list);
}

and your program will no-longer deadlock (it also does not execute any code after await TaskEx.WhenAll(list); but that is because this code is running during the shutdown process and when you await it lets the shutdown continue on processing, if it was placed elsewhere like a click event you would see more normal behavior).


Another option is have a 2nd "Main Thread" and dispatch the work to that. Often when something must be run on "the main" thread are actually saying they require to be run on "a STA Windows Message pumped that the object was initially created on" thread. Here is a example how to to it (taken from here)

private void runBrowserThread(Uri url) {
    var th = new Thread(() => {
        var br = new WebBrowser();
        br.DocumentCompleted += browser_DocumentCompleted;
        br.Navigate(url);
        Application.Run();
    });
    th.SetApartmentState(ApartmentState.STA);
    th.Start();
}

void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
    var br = sender as WebBrowser;
    if (br.Url == e.Url) {
        Console.WriteLine("Natigated to {0}", e.Url);
        Application.ExitThread();   // Stops the thread
    }
}
Community
  • 1
  • 1
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • This sounds a lot more promising as it solves partially my problem. I can't help to think I am using incorrectly TPL. If I want to post things onto the main thread, is this the best way to do it with Tasks? You pointed out another issue I am having (and I found it by testing the code a bit further) if the main thread is terminated before any of the tasks is executed the task stays scheduled and the application doesn't terminate as it can't release resources. – rodrigoelp Nov 12 '13 at 12:02
0

@HansPassant is correct; by blocking the dispatcher thread to wait on the tasks, you prevent the tasks from ever being executed. The simplest change you could probably make would be to replace Task.WaitAll(list) with:

_taskFactory.ContinueWhenAll(
    list,
    tasks => { /* resume here */ });

...and then move any code which followed the call to WaitAll() into the continuation. Remember to check the task results and respond appropriately to any exceptions that might have occurred.

But unless there is some tangible benefit to using Tasks that is not apparent in your example code, I would heed Hans' advice and simply forego the Tasks in favor of synchronous calls.

Mike Strobel
  • 25,075
  • 57
  • 69
  • Maybe my example isn't clear enough... My problem is the provided API has some thread affinity (to the main thread) otherwise the method throws an exception as I am violating their memory model. The service I am implementing is trying to wrap this API to marshall onto the main thread any call performed on the service (trying to fix the broken API). As I am using TaskFactory for some other work I thought that starting a task on the main thread was going to be easier and my wrapper was going to expose Tasks for any other code to continue on completion. – rodrigoelp Nov 12 '13 at 11:53