3

I need to make UI thread wait until a task array completes execution.The problem with below code is that - the tasks inturn invoke UI thread to write into textbox. How to fix this?

public partial class FormConsole : Form
{
    public FormConsole()
    {
        InitializeComponent();
    }
    void txtSayHello_Click(object sender, EventArgs e)
    {
        Class1 objclss = new Class1();
        objclss.formConsole = this;

        Task[] taa = new Task[4];
        taa[0] = new Task(() => objclss.DoSomeThigs("Hello world"));
        taa[1] = new Task(() => objclss.DoSomeThigs("Hello world1"));
        taa[2] = new Task(() => objclss.DoSomeThigs("Hello world2"));
        taa[3] = new Task(() => objclss.DoSomeThigs("Hello world3"));

        foreach(Task task in taa)
        {
            task.Start();
        }
        Task.WhenAll(taa);
        this.txtConsole.AppendText("All threads complete");
    }

    delegate void doStuffDelegate(string value);

    public void doStuff(string value)
    {        
        if (System.Windows.Forms.Form.ActiveForm.InvokeRequired && IsHandleCreated)
        {
            BeginInvoke(new doStuffDelegate(doStuff), value);        
        }
        else
            txtConsole.AppendText(value);        
    }

}

public class Class1
{
    public FormConsole formConsole;
    public void DoSomeThigs(string sampleText)
    {
        formConsole.doStuff(sampleText);
    }
}

o/p now : Console Redirection TestAll threads completeHello worldHello world1Hello world2Hello world3

o/p I want : Console Redirection TestHello worldHello world1Hello world2Hello world3All threads complete

What's the solution?

svick
  • 236,525
  • 50
  • 385
  • 514
Murali Uppangala
  • 884
  • 6
  • 22
  • 49

3 Answers3

5

Task.WhenAll returns a task that completes when all tasks passed to it complete. You have to await this task, otherwise the method will continue executing.

async void txtSayHello_Click(object sender, EventArgs e)
{
    ...
    await Task.WhenAll(taa);
    ...
}

There is a blocking version of this method - Task.WaitAll. It will block the current thread until all tasks are done, but it's not a good idea to block the UI thread.

Also, the preferred way to start a task on a thread pool thread is to use Task.Run.

Jakub Lortz
  • 14,616
  • 3
  • 25
  • 39
4

Task.WhenAll returns a Task representing the completion of all these tasks in the enumerable. You need to await that task as the method doesn't block the thread.

Turn txtSayHello_Click into an async void (which should only be used for event handlers) method and await the task returned from Task.WhenAll:

async void txtSayHello_Click(object sender, EventArgs e)
{
    // ...
    await Task.WhenAll(taa);
    // ...
}

Moreover, you should almost always avoid using the Task constructor. You should use Task.Factory.StartNew with TaskScheduler.FromSynchronizationContext if you need the task to run on the UI thread (that depends on what you actually do with FormConsole) or Task.Run if you don't. Meaning:

taa[0] = Task.Factory.StartNew(() => objclss.DoSomeThigs("Hello world"), CancellationToken.None, TaskCreationOptions.None,
            TaskScheduler.FromCurrentSynchronizationContext());

Or:

taa[0] = Task.Run(() => objclss.DoSomeThigs("Hello world"));
i3arnon
  • 113,022
  • 33
  • 324
  • 344
3

You're most likely confusing WhenAll() with WaitAll(). As already suggested you can use async with await or you can simply use :

A) Task.WhenAll(taa).Wait();

B) Task.WaitAll(taa);

But in your case this will block UI thread. So it's better to put rest of the code to Continuation Task and invoke UI operations with Control.Invoke() :

Task.WhenAll(taa).ContinueWith(t =>
{
   this.Invoke(() => this.txtConsole.AppendText("All threads complete"));
});
Fabjan
  • 13,506
  • 4
  • 25
  • 52
  • o/p after trying this (Not working in my case): Console Redirection TestAll threads completeHello worldHello world1Hello world2Hello world3. – Murali Uppangala Nov 27 '15 at 13:34
  • using only continueWith gives error `Cross-thread operation not valid: Control 'txtConsole' accessed from a thread other than the thread it was created on.` but using async-await works. – Murali Uppangala Nov 27 '15 at 14:06
  • 1
    @flute with async code that follows await statement is executed with first available thread from the `TreadPool` with priority set to thread that was used when async method was started. If UI thread enters method (which is most likely the case here) then async-await will attempt to use it to execute code after `await` but if UI thread is busy (even for a second) then you'll see this excepiton with async-await too. If you'll envelope your UI call with `this.Invoke(() => { MyCode... });` you'll have 100% that it'll be invoked on same thread. Updated my answer. – Fabjan Nov 27 '15 at 14:30