1

In this code Parallel.ForEach is not waiting. Seconds elapsed is immediately returning 0. This code is in a PCL:

private void Button_Clicked(object sender, EventArgs e)
{
    Stopwatch watch = new Stopwatch();
    watch.Start();

    List<Task> taskList = new List<Task>();
    Task taskA = new Task(ExecuteTaskAAsync);
    Task taskB = new Task(ExecuteTaskBAsync);
    taskList.Add(taskA);
    taskList.Add(taskB);

    Parallel.ForEach(taskList, t => t.Start());

    watch.Stop();
    System.Diagnostics.Debug.WriteLine("Seconds Elapsed: " + watch.Elapsed.Seconds);
}

private async void ExecuteTaskAAsync()
{
    for (int i = 0; i < 10; i++)
    {
        System.Diagnostics.Debug.WriteLine("Task A: [{0}]", i + 1);
        await Task.Delay(1000);
    }

    System.Diagnostics.Debug.WriteLine("Finished Task A!");
}

private async void ExecuteTaskBAsync()
{
    for (int i = 0; i < 10; i++)
    {
        System.Diagnostics.Debug.WriteLine("Task B: [{0}]", i + 1);
        await Task.Delay(1000);
    }

    System.Diagnostics.Debug.WriteLine("Finished Task B!");
}

Responsible for this behavior seems to be t.Start(), which starts a new thread and Parallel.ForEach has finished. I also tried it by using Task as return type and this code:

Parallel.Invoke(async() => await ExecuteTaskAAsync(), async() => await ExecuteTaskBAsync());

Again seconds elapsed is immediately returning 0. How can I run both tasks parallel?

testing
  • 19,681
  • 50
  • 236
  • 417

1 Answers1

3

The parallel is working just fine - it's starting both tasks in parallel, just as your code is asking it to. It's just not waiting for those tasks to complete (and even if it was, those tasks aren't waiting for your async void methods to complete).

Since you're dealing with asynchronous tasks, what you really want is asynchronous concurrency, not parallel concurrency. They're both kinds of concurrency (doing more than one thing at the same time), but parallelism is about spawning multiple threads, whereas asynchrony is about freeing up the current thread.

Asynchronous concurrency works by using Task.WhenAll, as such:

private async void Button_Clicked(object sender, EventArgs e)
{
  Stopwatch watch = new Stopwatch();
  watch.Start();

  Task taskA = ExecuteTaskAAsync();
  Task taskB = ExecuteTaskBAsync();

  await Task.WhenAll(taskA, taskB);

  watch.Stop();
  System.Diagnostics.Debug.WriteLine("Seconds Elapsed: " + watch.Elapsed.Seconds);
}

private async Task ExecuteTaskAAsync()
{
  for (int i = 0; i < 10; i++)
  {
    System.Diagnostics.Debug.WriteLine("Task A: [{0}]", i + 1);
    await Task.Delay(1000);
  }

  System.Diagnostics.Debug.WriteLine("Finished Task A!");
}

private async Task ExecuteTaskBAsync()
{
  for (int i = 0; i < 10; i++)
  {
    System.Diagnostics.Debug.WriteLine("Task B: [{0}]", i + 1);
    await Task.Delay(1000);
  }

  System.Diagnostics.Debug.WriteLine("Finished Task B!");
}

Also note that I changed the existing async void methods to async Task; this is because async void is unnatural and should be only used for event handlers. See my MSDN article on async best practices for more info.

As a final note, you should never use the Task constructor. I explain in detail on my blog, but it comes down to: there's always a better alternative.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • The goal of my code was to have multiple threads working parallel so that I can see how it behaves when a single source (in this case `Debug.WriteLine`) is accessed. Is this goal reached with your corrected version? – testing May 18 '16 at 15:40
  • No; the code I posted will all run on the UI thread. If you want to test multithreaded code, then use `Parallel` but remove all `async`/`await` and use `Thread.Sleep` instead of `Task.Delay`. – Stephen Cleary May 18 '16 at 15:59
  • [`Thread.Sleep`](http://stackoverflow.com/questions/35767643/using-thread-sleep-in-xamarin-forms) isn't available in my environment (Portable Class Library). I'll try it with `Device.StartTimer` or `Task.Delay(1000).Wait();`. How do I use `Parallel` then here? Especially when not using the `Task` constructor. – testing May 18 '16 at 16:11
  • @testing: `Parallel.Invoke(() => ExecuteTaskA(), () => ExecuteTaskB());` – Stephen Cleary May 18 '16 at 20:15