Need help with awaitable tasks

I'm used to doing things the old-fashioned way with BackgroundWorker objects and multiple threads. I'm trying to figure out how to use the (seemingly better) awaitable task method of doing things. I created a UserControl with 3 progress bars. The UserControl code looks like this:
	public partial class MultiTaskThingy : UserControl
	{
		public MultiTaskThingy()
		{
			InitializeComponent();
		}

		public async Task<AsyncStatus> RunMultipleTasks(AsyncProgress<AsyncStatus> progress, CancellationToken cToken)
		{
			AsyncThingy thingy = new AsyncThingy();
			AsyncStatus overallStatus = new AsyncStatus();
			var localProgress = new AsyncProgress<AsyncStatus>(ReportProgress);
			List<Task<AsyncStatus>> taskList = new List<Task<AsyncStatus>>() { thingy.PseudoTask("1", localProgress, cToken), thingy.PseudoTask("2", localProgress, cToken), thingy.PseudoTask("3", localProgress, cToken) };
			try
			{
				await Task.WhenAll(taskList);
			}
			catch (Exception ex)
			{
				System.Diagnostics.Debug.Print(ex.Message);
			}
			return overallStatus;
		}

		void ReportProgress(AsyncStatus value)
		{
			switch (value.Context)
			{
				case "1":
					progressBar1.Invoke(pb => { pb.Value = value.PercentComplete; });
					break;
				case "2":
					progressBar2.Invoke(pb => { pb.Value = value.PercentComplete; });
					break;
				case "3":
					progressBar3.Invoke(pb => { pb.Value = value.PercentComplete; });
					break;
				default:
					throw new ArgumentOutOfRangeException($"Context {value.Context} is not valid");
			}
		}
	}

Open in new window

I then have that UserControl on a Windows Form with a single button that kicks things off. The button code is:
		private async void buttonDoManyThings_Click(object sender, EventArgs e)
		{
			_cts = new CancellationTokenSource();
			var progressIndicator = new AsyncProgress<AsyncStatus>(ReportProgress);
			await Task.Run(() => multiTaskThingy.RunMultipleTasks(progressIndicator, _cts.Token));
			_cts.Dispose();
			_cts = null;
		}

Open in new window

All 3 tasks run and the calling UI thread isn't blocked. However, the tasks run sequentially rather than concurrently. This isn't what I expected. Can anyone tell me what I'm doing wrong?

FYI, The AsyncThingy class contains the PseudoTask method which is nothing more than a loop counter with an arbitrary length between 5 and 15 seconds. It just reports a progress back to the caller.
LVL 21
Russ SuterAsked:
Who is Participating?
 
käµfm³d 👽Commented:
So as written, I do not think your PseudoTaskX methods are very useful. You've marked them as async, but you're not using an await. The whole purpose of marking a method as async is so that you can use the await keyword within the method. You can actually see this within the warning that VS adds to the method name (i.e. the green squiggly). If you're not going to use await, then don't tack on the async. (This won't affect the fact that you're returning a Task...assuming you are returning one; I've changed your methods slightly below.)

public class AsyncThingy
{
    Random rand = new Random();

    public AsyncStatus PseudoTask(string someInput, AsyncProgress<AsyncStatus> progress, CancellationToken cToken)
    {
        AsyncStatus status = new AsyncStatus();
        status.Context = someInput;
        status.Status = "Initializing 1...";
        progress?.Report(status);
        Thread.Sleep(rand.Next(250, 1000));
        status.Status = "Working 1...";
        progress?.Report(status);
        Thread.Sleep(rand.Next(250, 1000));
        int taskRuntime = new Random().Next(5, 15);
        double currentTimeIndex = 0;
        DateTime timeToStop = DateTime.Now.AddSeconds(taskRuntime);
        do
        {
            cToken.ThrowIfCancellationRequested();
            currentTimeIndex = (timeToStop - DateTime.Now).TotalSeconds;
            status.PercentComplete = (int)((taskRuntime - currentTimeIndex) / taskRuntime * 100);
            progress?.Report(status);
            Thread.Sleep(rand.Next(250, 1000));
        } while (DateTime.Now < timeToStop);
        status.PercentComplete = 100;
        status.Status = "Finished";
        progress.Report(status);
        return status;
    }

    public AsyncStatus PseudoTask2(string someInput, AsyncProgress<AsyncStatus> progress, CancellationToken cToken)
    {
        AsyncStatus status = new AsyncStatus();
        status.Status = "Initializing 2...";
        status.Context = someInput;
        progress?.Report(status);
        Thread.Sleep(rand.Next(250, 1000));
        status.Status = "Working 2...";
        progress?.Report(status);
        int taskRuntime = new Random().Next(5, 15);
        double currentTimeIndex = 0;
        DateTime timeToStop = DateTime.Now.AddSeconds(taskRuntime);
        do
        {
            cToken.ThrowIfCancellationRequested();
            currentTimeIndex = (timeToStop - DateTime.Now).TotalSeconds;
            status.PercentComplete = (int)((taskRuntime - currentTimeIndex) / taskRuntime * 100);
            progress?.Report(status);
            Thread.Sleep(rand.Next(250, 1000));
        } while (DateTime.Now < timeToStop);
        status.PercentComplete = 100;
        status.Status = "Finished";
        progress.Report(status);
        return status;
    }

    public AsyncStatus PseudoTask3(string someInput, AsyncProgress<AsyncStatus> progress, CancellationToken cToken)
    {
        AsyncStatus status = new AsyncStatus();
        status.Status = "Initializing 3...";
        status.Context = someInput;
        progress?.Report(status);
        Thread.Sleep(rand.Next(250, 1000));
        status.Status = "Working 3...";
        progress?.Report(status);
        int taskRuntime = new Random().Next(5, 15);
        double currentTimeIndex = 0;
        DateTime timeToStop = DateTime.Now.AddSeconds(taskRuntime);
        do
        {
            cToken.ThrowIfCancellationRequested();
            currentTimeIndex = (timeToStop - DateTime.Now).TotalSeconds;
            status.PercentComplete = (int)((taskRuntime - currentTimeIndex) / taskRuntime * 100);
            progress?.Report(status);
            Thread.Sleep(rand.Next(250, 1000));
        } while (DateTime.Now < timeToStop);
        status.PercentComplete = 100;
        status.Status = "Finished";
        progress.Report(status);
        return status;
    }
}

Open in new window


Now the work that's being done inside of each "pseudotask" appears to be all CPU-bound, especially considering your usage of Thread.Sleep. For this reason, it would seem to make more sense to use a Task.Run call outside of these methods within RunMutlipleTasks:

public async Task<AsyncStatus> RunMultipleTasks(AsyncProgress<AsyncStatus> progress, CancellationToken cToken)
{
    AsyncThingy thingy = new AsyncThingy();
    AsyncStatus overallStatus = new AsyncStatus();
    var localProgress = new AsyncProgress<AsyncStatus>(ReportProgress);
    List<Task<AsyncStatus>> taskList = new List<Task<AsyncStatus>>()
    {
        Task.Run(() => thingy.PseudoTask("1", localProgress, cToken), cToken),
        Task.Run(() => thingy.PseudoTask2("2", localProgress, cToken), cToken),
        Task.Run(() => thingy.PseudoTask3("3", localProgress, cToken), cToken),
    };

    try
    {
        await Task.WhenAll(taskList);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.Print(ex.Message);
    }

    return overallStatus;
}

Open in new window


You'll get the benefit of running on a new thread for each call, and you'll get back a task that you can await. You can ditch the Task.Run call that's in the button click handler--just await the call to the method directly:

private async void buttonDoManyThings_Click(object sender, EventArgs e)
{
    buttonDoManyThings.Enabled = false;
    _cts = new CancellationTokenSource();
    var progressIndicator = new AsyncProgress<AsyncStatus>(ReportProgress);
    await multiTaskThingy.RunMultipleTasks(progressIndicator, _cts.Token);
    _cts.Dispose();
    _cts = null;
    buttonDoManyThings.Enabled = true;
}

Open in new window


Screenshot
This should get you closer to what you're after. I have to slide in two caveats, though:  It's been a few years since I've done WinForms, and my thread-fu is not strong. The above seems to work and mimic what you're looking for, but I haven't exhaustively tested it. You'll surely want to before migrating something similar to production.
0
 
käµfm³d 👽Commented:
Async code is for I/O, not CPU. So what is it exactly that your AsyncThingy does?

Think of async like going to the doctor, getting tests run, and then the doctor calling you when the results are ready. The async bit is the part where you go on about your life while you wait for them to call you back.

Think of CPU-related (a.k.a. threading) as cleaning your house. You can clean your house all by yourself, but it will take longer. If you split the work between you and your roommate, then the house gets cleaned faster, with each of you having your own, distinct work to do.
0
 
Russ SuterAuthor Commented:
That explanation does help quite a bit. Thanks for that. I realize now what people were meaning when they referred to non-blocking functions. These are just functions that don't block the UI thread. However, in this case the separate executions should be able to be run independent of one another and I assumed that this would run them concurrently. Since you asked, here's the function body of AsyncThingy. It's just a quick and dirty class I threw together to simulate some time-consuming process that would run.
	public class AsyncThingy
	{
		public async Task<AsyncStatus> PseudoTask(string someInput, AsyncProgress<AsyncStatus> progress, CancellationToken cToken)
		{
			AsyncStatus status = new AsyncStatus();
			status.Status = "Initializing...";
			progress?.Report(status);
			status.Context = someInput;
			Thread.Sleep(1000);
			status.Status = "Working...";
			int taskRuntime = new Random().Next(5, 15);
			double currentTimeIndex = 0;
			DateTime timeToStop = DateTime.Now.AddSeconds(taskRuntime);
			do
			{
				cToken.ThrowIfCancellationRequested();
				currentTimeIndex = (timeToStop - DateTime.Now).TotalSeconds;
				status.PercentComplete = (int)((taskRuntime - currentTimeIndex) / taskRuntime * 100);
				progress?.Report(status);
				Thread.Sleep(250);
			} while (DateTime.Now < timeToStop);
			status.PercentComplete = 100;
			status.Status = "Finished";
			progress.Report(status);
			return status;
		}

		public async Task<AsyncStatus> PseudoTask2(string someInput, AsyncProgress<AsyncStatus> progress, CancellationToken cToken)
		{
			AsyncStatus status = new AsyncStatus();
			status.Status = "Initializing...";
			progress?.Report(status);
			status.Context = someInput;
			Thread.Sleep(1000);
			status.Status = "Working...";
			int taskRuntime = new Random().Next(5, 15);
			double currentTimeIndex = 0;
			DateTime timeToStop = DateTime.Now.AddSeconds(taskRuntime);
			do
			{
				cToken.ThrowIfCancellationRequested();
				currentTimeIndex = (timeToStop - DateTime.Now).TotalSeconds;
				status.PercentComplete = (int)((taskRuntime - currentTimeIndex) / taskRuntime * 100);
				progress?.Report(status);
				Thread.Sleep(250);
			} while (DateTime.Now < timeToStop);
			status.PercentComplete = 100;
			status.Status = "Finished";
			progress.Report(status);
			return status;
		}

		public async Task<AsyncStatus> PseudoTask3(string someInput, AsyncProgress<AsyncStatus> progress, CancellationToken cToken)
		{
			AsyncStatus status = new AsyncStatus();
			status.Status = "Initializing...";
			progress?.Report(status);
			status.Context = someInput;
			Thread.Sleep(1000);
			status.Status = "Working...";
			int taskRuntime = new Random().Next(5, 15);
			double currentTimeIndex = 0;
			DateTime timeToStop = DateTime.Now.AddSeconds(taskRuntime);
			do
			{
				cToken.ThrowIfCancellationRequested();
				currentTimeIndex = (timeToStop - DateTime.Now).TotalSeconds;
				status.PercentComplete = (int)((taskRuntime - currentTimeIndex) / taskRuntime * 100);
				progress?.Report(status);
				Thread.Sleep(250);
			} while (DateTime.Now < timeToStop);
			status.PercentComplete = 100;
			status.Status = "Finished";
			progress.Report(status);
			return status;
		}
	}

	public class AsyncStatus
	{
		#region Public Properties

		public string Message { get; set; }
		public int PercentComplete { get; set; }
		public string Status { get; set; }
		public string Context { get; set; }

		#endregion
	}

	public class AsyncProgress<T> : Progress<T>
	{
		#region Public Properties

		public string Context { get; set; }

		#endregion

		#region Constructors

		public AsyncProgress(Action<T> handler)
			: base(handler)
		{

		}

		#endregion

		public void Report(T value)
		{
			base.OnReport(value);
		}
	}

Open in new window

Ultimately, my goal is to have several functions running simultaneously each of which calculates some stuff. The real-world application of this is that I have a process which calculates a result based upon about half a dozen calculated result sets. Right now, the system calculates each subset one at a time and then aggregates them into the final result set. However, almost all of the subset processing can be done completely independently of the others. Leveraging multi-threaded processing can dramatically speed up the overall time to calculate the final product. I can certainly do it the true threading way but was hoping that, because of its apparent ease of use, I could use the async/await model. Are you saying I'm barking up the wrong tree here? What about this "Parallel" thing I've been reading about?
0
Get your problem seen by more experts

Be seen. Boost your question’s priority for more expert views and faster solutions

 
käµfm³d 👽Commented:
These are just functions that don't block the UI thread.
Well, it's really any thread, but yes.

...each of which calculates some stuff.
That sounds CPU, not I/O.

Leveraging multi-threaded processing can dramatically speed up the overall time to calculate the final product.
Certainly. Just remember that "multi-threaded" does not mean "asynchronous".

What about this "Parallel" thing I've been reading about?
Parallel means exactly what the dictionary says it means (in terms of time, not in terms of two lines!!):  Doing multiple things concurrently. If you're processing multiple things on different threads at the same time, then you're working in parallel. If you've got two web requests going on at the same time, each of those is happening in parallel, but as far as your system, you are (should be?) doing other work while you asynchronously wait for those requests to return. Think about tasks and asynchronicity as, "I know that this thing has a result, but I don't know it yet. I will know, however, the result at some point in the future." At the point at which your code cannot possibly continue without knowing the result await the task.
0
 
käµfm³d 👽Commented:
Also, let's continue on with the house cleaning analogy. You can certainly be both multi-threaded and asynchronous. If you're cleaning the bedroom, and your roommate is cleaning the living room, then maybe at present he's using the vacuum cleaner. Obviously, you can't use the vacuum cleaner while he's using it, so you start making the bed. He keeps the vacuum so long that you finish the bedroom. Now you sit down and read a magazine while you await the vacuum. He calls out to you when he's done using the vacuum. The two of you are still threads doing independent work, but now his calling out to you (I/O) signals you to start vacuuming. You knew the vacuum would be available at some point in the future, but you did not know exactly when.
0
 
Russ SuterAuthor Commented:
Yep, I gotcha. I totally get the multithreading concept. What I was wondering was if, in my above example, is there a way to run these processes in parallel? The idea, as I indicated, is that each of these processes can be run independently but the next step cannot be started until all of these are finished. This approach (or so I thought) seemed like a very easy way to accomplish that until I discovered that the functions ran sequentially rather than concurrently. So this brings me back to the original question. Can the above code be modified such that the 3 function calls run concurrently but the overall execution waits until all 3 have completed? If the answer to that question is no then I have to go back to handling multiple threads like I'm used to doing. It's a bit of a pain but I have managed it before and can do so again.
0
 
käµfm³d 👽Commented:
I can't do it right this second, but let me try putting your code into an actual project so I can see it better as well as try to run it. Maybe later tonight (EST)?
0
 
Russ SuterAuthor Commented:
That would be awesome. Thanks.

Fortunately I'm not in desperate need of this solution today so take your time. Thanks again.
0
 
Russ SuterAuthor Commented:
That's absolutely what I was looking for. Thank you very much. I see your point about the unnecessary async keyword. You've bridged the gap appropriately for me. I can now run the tasks concurrently and have the outer call wait until they've all finished.

The PseudoTask methods were written specifically so that they would be CPU-bound. They are a quick and dirty example of the real work that I'll be doing, which as stated previously, involves calculations on fairly large subsets of data.

If I understand this correctly, one of the beautiful things about this approach is that it will automatically utilize additional CPU cores when appropriate and beneficial.

Now that I have this piece I'll get to work on replacing PseudoTask with something that actually does some work.

Thanks.
0
 
käµfm³d 👽Commented:
If I understand this correctly, one of the beautiful things about this approach is that it will automatically utilize additional CPU cores when appropriate and beneficial.
Task.Run queues work to the thread pool, so maybe there's a chance that all work could end up on the same thread (not at the same time, obviously). But I'd bet that in practice you get a new thread for each call.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.