1

We recently adopted the TPL as the toolkit for running some heavy background tasks.

These tasks typically produce a single object that implements IDisposable. This is because it has some OS handles internally.

What I want to happen is that the object produced by the background thread will be properly disposed at all times, also when the handover coincides with application shutdown.

After some thinking, I wrote this:

    private void RunOnUiThread(Object data, Action<Object> action)
    {
        var t = Task.Factory.StartNew(action, data, CancellationToken.None, TaskCreationOptions.None, _uiThreadScheduler);
        t.ContinueWith(delegate(Task task)
            {
                if (!task.IsCompleted)
                {
                    DisposableObject.DisposeObject(task.AsyncState);
                }
            });            
    }

The background Task calls RunOnUiThread to pass its result to the UI thread. The task t is scheduled on the UI thread, and takes ownership of the data passed in. I was expecting that if t could not be executed because the ui thread's message pump was shut down, the continuation would run, and I could see that that the task had failed, and dispose the object myself. DisposeObject() is a helper that checks if the object is actually IDisposable, and non-null, prior to disposing it.

Sadly, it does not work. If I close the application after the background task t is created, the continuation is not executed.

I solved this problem before. At that time I was using the Threadpool and the WPF Dispatcher to post messages on the UI thread. It wasn't very pretty, but in the end it worked. I was hoping that the TPL was better at this scenario. It would even be better if I could somehow teach the TPL that it should Dispose all leftover AsyncState objects if they implement IDisposable.

So, the code is mainly to illustrate the problem. I want to learn about any solution that allows me to safely handover Disposable objects to the UI thread from background Tasks, and preferably one with as little code as possible.

  • 1
    There just no point to this. You dispose objects so they don't linger longer than necessary. Nothing lingers after a process shutdown. Windows cleans up any handles left open. Which in itself won't be necessary, the AppDomain runs the finalizer before shutting down. There are better things to fret about, like allowing an app to shutdown while there's still an unfinished task lying around. – Hans Passant Feb 11 '12 at 21:23

3 Answers3

1

When a process closes, all of it's kernel handles are automatically closed. You shouldn't need to worry about this:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms686722(v=vs.85).aspx

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
0

Have a look at the RX library. This may allow you to do what you want.

Chriseyre2000
  • 2,053
  • 1
  • 15
  • 28
  • Actually I have looked at it. Do you know about any special handling for `IDisposable` objects in there? –  Feb 10 '12 at 22:46
  • No, but since the IObservable will always call either OnError or OnCompleted then you can dispose it there. – Chriseyre2000 Feb 10 '12 at 22:50
  • I see.... The issue is that this is not an exception in the producer, which is what OnError is for, as I understand it. But I will try it anyway... unless someone comes up with a better suggestion. –  Feb 10 '12 at 22:58
  • Also see http://stackoverflow.com/questions/4085939/who-should-call-dispose-on-idisposable-objects-when-passed-into-another-object – Chriseyre2000 Feb 11 '12 at 11:02
  • Way late to party but for future readers and @Chriseyre2000. There is in fact special handling. Create your Observable sequence with something like: `Observable.Using(() => new MyDisposable(), d => action(d).ToObservable())` Once the sequence completes the disposable objects will be disposed. Note you may also want to use `Observable.Defer` and pass the sequence into that in a similar manner. Also `ToObservable` is in the `System.Reactive.Threading.Tasks` namespace. – Lex Aug 24 '12 at 09:26
0

From MSDN:

IsCompleted will return true when the Task is in one of the three final states: RanToCompletion, Faulted, or Canceled

In other words, your DisposableObject.DisposeObject will never be called, because the continuation will always be scheduled after one of the above conditions has taken place. I believe what you meant to do was :

t.ContinueWith(t => DisposableObject.DisposeObject(task.AsyncState),
               TaskContinuationOptions.NotOnRanToCompletion)

(BTW you could have simply captured the data variable rather than using the AsyncState property)

However I wouldn't use a continuation for something that you want to ensure happens at all times. I believe a try-finally block will be more fitting here:

private void RunOnUiThread2(Object data, Action<Object> action)
{
    var t = Task.Factory.StartNew(() => 
    {
        try
        {
            action(data);
        }
        finally
        {
            DisposableObject.DisposeObject(task.AsyncState); 
            //Or use a new *foreground* thread if the disposing is heavy
        }
    }, CancellationToken.None, TaskCreationOptions.None, _uiThreadScheduler);
}
Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198