开发者

How to get notification that a System.Threading.Tasks.Task has completed

I am currently replacing some home baked task functionality with a new implementation using the new System.Threading.Tasks functionality found in .net 4.

I have a slight issue though, and although I can think of some solutions I wo开发者_JAVA技巧uld like some advice on which is generally the best way to do it, and if I am missing a trick somewhere.

What I need is for an arbitrary process to be able to start a Task but then carry on and not wait for the Task to finish. Not a problem, but when I then need to do something with the result of a task i'm not quite sure the best way of doing it.

All the examples I have seen use either Wait() on the task until it completes or references the Result parameter on the task. Both of these will block the thread which started the Task, which I don't want.

Some solutions I have thought of:

  1. Create a new thread and start the task on that, then use Wait() or .Result to block the new thread and sync the result back to the caller somehow, possibly with polling to the tasks IsCompleted parameter.

  2. Have a 'Notify Completed' task which I can start after completion of the task I want to run which then raises a static event or something.

  3. Pass a delegate into the input of the task and call that to notify that the task is finished.

I can think or pros and cons to all of them, but I especially don't like the idea of having to explicitly create a new thread to start the task on when the one of the aims of using the Task class in the first place is to abstract away from direct Thread usage.

Any thoughts about the best way? Am I missing something simple? Would a 'Completed' event be too much to ask for :)? (Sure there is a good reason why there isn't one!)


I suspect you're looking for Task.ContinueWith (or Task<T>.ContinueWith). These basically say, "When you've finished this task, execute this action." However, there are various options you can specify to take more control over it.

MSDN goes into a lot more detail on this in "How to: Chain Multiple Tasks With Continuations" and "Continuation Tasks".


In modern C#, one no longer needs to call ContinueWith() explicitly. An alternative to the original accepted answer would be to simply create an async method that awaits the Task in question, and does whatever it wants when the Task completes.

For example, suppose you want to raise an event called TaskCompleted when the Task completes. You would write a method like:

async Task RaiseEventWhenTaskCompleted(Task task)
{
    await task;
    TaskCompleted?.Invoke(this, EventArgs.Empty);
}

To "register" the wait, just call the above method. Add exception handling as desired, either in the method above, or in some code that will eventually observe the Task returned by the above method.


 Task task = Task.Run ( () => { Thread.Sleep ( 2000 ); } );

 task.GetAwaiter ().OnCompleted ( () => 
   {
     MessageBox.Show ( "the task completed in the main thread", "");
   } );


You can apply a task continuation.

Alternatively, Task implements IAsyncResult, so you can use the standard approaches for that interface (blocking, polling, or waiting on its WaitHandle).


I created a small example illustrating Jon Skeet's answer, which I'd like to share with you:

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    static void Main(string[] args)
    {
        for (int cnt = 0; cnt < NumTasks; cnt++)
        {
            var task = new Task<int>(DoSomething); // any other type than int is possible
            task.ContinueWith(t => Console.WriteLine($"Waited for {t.Result} milliseconds."));
            task.Start(); // fire and forget
        }

        PlayMelodyWhileTasksAreRunning();       
    }
    static int NumTasks => Environment.ProcessorCount;

    static int DoSomething()
    {
        int milliSeconds = random.Next(4000) + 1000;
        Console.WriteLine($"Waiting for {milliSeconds} milliseconds...");
        Thread.Sleep(milliSeconds);

        return milliSeconds; // make available to caller as t.Result
    }
    static Random random = new Random();

    static void PlayMelodyWhileTasksAreRunning()
    {
        Console.Beep(587, 200); // D
        Console.Beep(622, 200); // D#
        Console.Beep(659, 200); // E
        Console.Beep(1047, 400); // C
        Console.Beep(659, 200); // E
        Console.Beep(1047, 400); // C
        Console.Beep(659, 200); // E
        Console.Beep(1047, 1200); // C
        Console.Beep(1047, 200); // C
        Console.Beep(1175, 200); // D
        Console.Beep(1245, 200); // D#
        Console.Beep(1319, 200); // E
        Console.Beep(1047, 200); // C
        Console.Beep(1175, 200); // D
        Console.Beep(1319, 400); // E
        Console.Beep(988, 200); // H
        Console.Beep(1175, 400); // D
        Console.Beep(1047, 1600); // C
    }
}


You can use the ContinueWith function with your routine as a first argument, and a task scheduler as the second argument given by TaskScheduler.FromCurrentSynchronizationContext().

It goes like this:

var task1 = new Task(() => {do_something_in_a_remote_thread();} );

task1.ContinueWith(() =>  {do_something_in_the_ui_thread();},
TaskScheduler.FromCurrentSynchronizationContext());
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜