2

I'm stuck with await. I want my task to report some progress to gui with fashion way - ContinueWith and FromCurrentSynchronizationContext.

But GUI is blocked and does not refresh untill all tasks are completed. How do I fix this problem?

I think the reason is because tasks are running in the same pool and refresh gui tasks are added to the end of the queue. But, I don't know how to do it properly due to lack of experience

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Linq;
using System.Linq.Expressions;

namespace AsyncCallbackSample
{
    public partial class MainForm : Form
    {
        private readonly Random _random = new Random();

        public MainForm()
        {
            InitializeComponent();
        }

        private async void OnGoButtonClick(object sender, EventArgs e)
        {
            var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

            await Task.WhenAll(_listBox.Items
                .OfType<string>() 
                .Select(
                    taskArgument => 
                    Task
                        .FromResult(DoLongTermApplication(taskArgument))
                        .ContinueWith(previousTask => _listBox.Items[_listBox.Items.IndexOf(taskArgument)] = previousTask.Result, uiScheduler) // refreshing the gui part while all other staff is in progress.
                        )
                .ToArray());
        }

        private string DoLongTermApplication(string taskInformation)
        {
            Thread.Sleep(1000 + _random.Next(1000));
            return $"Processed {taskInformation}";
        }
    }
}
Dennis van Gils
  • 3,487
  • 2
  • 14
  • 35
Siarhei Kuchuk
  • 5,296
  • 1
  • 28
  • 31

2 Answers2

3

You should follow these guidelines:

  • Don't use ContinueWith, ever. Use await instead.
  • Don't use TaskSchedulers, unless you absolutely have to. Use await instead.
  • Use Task.Run to run synchronous code on a thread pool thread. Task.FromResult is not appropriate for this.

Combining these:

private async void OnGoButtonClick(object sender, EventArgs e)
{
  await Task.WhenAll(_listBox.Items.OfType<string>() 
      .Select(taskArgument => ProcessAsync(taskArgument)));
}

private async Task ProcessAsync(string taskArgument)
{
  var result = await Task.Run(() => DoLongTermApplication(taskArgument));
  _listBox.Items[_listBox.Items.IndexOf(taskArgument)] = result;
}

private string DoLongTermApplication(string taskInformation)
{
  Thread.Sleep(1000 + _random.Next(1000));
  return $"Processed {taskInformation}";
}

Alternatively, if your DoLongTermApplication can be made truly asynchronous (e.g., by replacing Thread.Sleep with Task.Delay), then you don't need Task.Run either:

private async void OnGoButtonClick(object sender, EventArgs e)
{
  await Task.WhenAll(_listBox.Items.OfType<string>() 
      .Select(taskArgument => ProcessAsync(taskArgument)));
}

private async Task ProcessAsync(string taskArgument)
{
  var result = await DoLongTermApplicationAsync(taskArgument);
  _listBox.Items[_listBox.Items.IndexOf(taskArgument)] = result;
}

private async Task<string> DoLongTermApplicationAsync(string taskInformation)
{
  await Task.Delay(1000 + _random.Next(1000)).ConfigureAwait(false);
  return $"Processed {taskInformation}";
}

Since you're new to async, I recommend reading my async intro blog post and following up with my MSDN article on async best practices.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Wov, i didn't expect such a huge comment even with code snippets and best pratice links for my problems with code... Thanks. – Siarhei Kuchuk Feb 25 '16 at 20:11
2

Use Thread.Sleep when you want to block current (UI in your case) thread. See: When to use Task.Delay, when to use Thread.Sleep?

Try something along these lines:

 private async void OnGoButtonClick(object sender, EventArgs e)
 {
            var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

            await Task.WhenAll(_listBox.Items
                .OfType<string>() 
                .Select(
                    taskArgument => 
                    Task.Run(async () => await DoLongTermApplicationAsync(taskArgument))
                        .ContinueWith(previousTask => _listBox.Items[_listBox.Items.IndexOf(taskArgument)] = previousTask.Result, uiScheduler) // refreshing the gui part while all other staff is in progress.
                        )
                .ToArray());
   }

   private async Task<string> DoLongTermApplicationAsync(string taskInformation)
   {
        await Task.Delay(1000 + _random.Next(1000));
        return $"Processed {taskInformation}";
   }
Community
  • 1
  • 1
Milen
  • 8,697
  • 7
  • 43
  • 57
  • Thank you soo much. As i understood problem in my code was that i needed to launch in default TaskFactory the initial task that was lambda. Also thanks for hinting about Task.Delay. I completely forgot about it. – Siarhei Kuchuk Feb 25 '16 at 16:34