Async with multiple UI objects

David L. Hansen
David L. Hansen used Ask the Experts™
on
So I've been playing with Async and have had some success in making an asynchronous app. My practice code implements this:
Await Task.Run(Sub()
                  LongOperation()
           End Sub)

Open in new window

 
This however, only allows the UI to receive focus when one other operation is underway (useful, but too limited for my use). I need to be able to have the UI receive focus while two other operations are underway. And it would be nice if these other operations could each influence the UI (form).

I've tried a couple other Async methods but when I do, the first and second operations always occur synchronously.

Can someone provide a simple example where Asyc kicks off multiple operations during runtime, and where the user still has control of the UI?
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
The following calls an asynchronous method which issues a call-back when each of 5 tasks completes ...
void Main()
{
	List<Task> tasks = Enumerable.Range(1, 5).Select(i => Task.Run(() => Thread.Sleep(i * 250 + 50))).ToList();
	DoTheAsyncWork(tasks, WriteResult);
	Console.WriteLine("I'm not waiting for them ...");	
}

public void WriteResult(string Result)
{
	Console.WriteLine(Result);
}

public async Task DoTheAsyncWork(List<Task> tasks, Action<string> WriteResult)
{
	while (tasks.Count() > 0)
	{
		Task FirstFinished = await Task.WhenAny(tasks.ToArray());
		tasks.Remove(FirstFinished);
		WriteResult($"Wait ended because task {FirstFinished.Id} completed.");
	}
}

Open in new window

Mike,

I've taken your code and tried to run it as strait C# and also as converted VB. I think it's close but errors still exist. I've tried to find work-arounds but to no avail.

Here's the converted VB and its error message:
    Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim tasks As List(Of Task) = Enumerable.Range(1, 5).[Select](Function(i) Task.Run(Function() Threading.Thread.Sleep(i * 250 + 50))).ToList()
        Await DoTheAsyncWork(tasks, AddressOf WriteResult)
        Console.WriteLine("I'm not waiting for them ...")
    End Sub

Open in new window

The sleep command won't compile, showing an "Expression does not produce a value" error.

Did your C# code compile?
ǩa̹̼͍̓̂ͪͤͭ̓u͈̳̟͕̬ͩ͂̌͌̾̀ͪf̭̤͉̅̋͛͂̓͛̈m̩̘̱̃e͙̳͊̑̂ͦ̌ͯ̚d͋̋ͧ̑ͯ͛̉Glanced up at my screen and thought I had coded the Matrix...  Turns out, I just fell asleep on the keyboard.
Most Valuable Expert 2011
Top Expert 2015

Commented:
Change Function to Sub in your lambda.

i.e.

...Task.Run(Sub() Threading.Thread...

Open in new window

Fundamentals of JavaScript

Learn the fundamentals of the popular programming language JavaScript so that you can explore the realm of web development.

So it compiles now and runs, although it behaves synchronously. One through 10 (I changed it to 10) report one after the other, then the "I'm not waiting for them.." message is displayed.

Here is the code:
Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    End Sub


    Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim tasks As List(Of Task) = Enumerable.Range(1, 10).[Select](Function(i) Task.Run(Sub() Threading.Thread.Sleep(i * 250 + 50))).ToList()
        Await DoTheAsyncWork(tasks, AddressOf WriteResult)
        'Console.WriteLine("I'm not waiting for them ...")
        Me.Label1.Text = "I'm not waiting for them ..."
        Me.Label1.Refresh()
    End Sub

    Public Sub WriteResult(Result As String)
        Console.WriteLine(Result)
    End Sub

    Public Async Function DoTheAsyncWork(tasks As List(Of Task), WriteResult As Action(Of String)) As Task
        While tasks.Count() > 0
            Dim FirstFinished As Task = Await Task.WhenAny(tasks.ToArray())
            tasks.Remove(FirstFinished)
            'WriteResult("Wait ended because task {FirstFinished.Id} completed.")
            Me.Label2.Text = "Wait ended because task" & FirstFinished.Id.ToString & " completed."
            Me.Label2.Refresh()
        End While
    End Function
End Class

Open in new window


thx in advance.
Hi David,
Yes, it did compile - and work - in c#.
I translated to VB (a trip down memory lane) and it still works.
Your VB needed a little fixing - here's the equivalent of what I posted in c#

Sub Main
	Dim tasks = Enumerable.Range(1, 5).[Select](Function(i)  Task.Run(Sub() Thread.Sleep(i * 250 + 50))).ToList()
	DoTheAsyncWork(tasks, AddressOf WriteResult) 
	Console.WriteLine("I'm not waiting for them ...")
End Sub

Public Sub WriteResult( Result As String)
	Console.WriteLine(Result)
End	Sub

Public Async Function  DoTheAsyncWork(tasks As List( Of Task), WriteResult As Action(Of String))  As Task 'List<Task> tasks, Action<string> WriteResult)
	While (tasks.Count() > 0)
	
		Dim FirstFinished = Await Task.WhenAny(tasks.ToArray())
		tasks.Remove(FirstFinished)
		WriteResult($"Wait ended because task {FirstFinished.Id} completed.")
	End While
End Function

Open in new window


This produced the following output:

I'm not waiting for them ...
Wait ended because task 29 completed.
Wait ended because task 31 completed.
Wait ended because task 51 completed.
Wait ended because task 53 completed.
Wait ended because task 55 completed

As an aside, I use LinqPad to create/test snippet of code like  this - works great for both c# and VB
ǩa̹̼͍̓̂ͪͤͭ̓u͈̳̟͕̬ͩ͂̌͌̾̀ͪf̭̤͉̅̋͛͂̓͛̈m̩̘̱̃e͙̳͊̑̂ͦ̌ͯ̚d͋̋ͧ̑ͯ͛̉Glanced up at my screen and thought I had coded the Matrix...  Turns out, I just fell asleep on the keyboard.
Most Valuable Expert 2011
Top Expert 2015

Commented:
@MikeToole

You should really be using a thread-safe collection to store your tasks, since you are modifying that collection while threads are running.
@käµfm³d

My answer was aimed at the basic question.
Thread-safety is an extra  - very important - consideration

Mike
Can I impose on your kindness a bit longer (both of you)? I'm seeing some of what's going on here but not all. Even setting aside thread-safe practices, there are still a point of confusion.

The working code now has a warning from the compiler and I'm not sure why. Someone else already put the following question to some other experts which expresses my confusion:
The whole point of Async is that it's asynchronous. I don't want it to block, because I want to get back to the MainWorkOfApplicationIDontWantBlocked ASAP and let GetNameAsync do its thing in the background. However, calling it this way gives me a compiler warning on the GetNameAsync line:

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

I'm perfectly aware that "execution of the current method continues before the call is completed". That's the point of asynchronous code, right?
One of the experts suggested that the warning (explained in the quote above) exists because most of the time  we should await the tasks to finish before proceeding. Doesn't that defeat the purpose?
ǩa̹̼͍̓̂ͪͤͭ̓u͈̳̟͕̬ͩ͂̌͌̾̀ͪf̭̤͉̅̋͛͂̓͛̈m̩̘̱̃e͙̳͊̑̂ͦ̌ͯ̚d͋̋ͧ̑ͯ͛̉Glanced up at my screen and thought I had coded the Matrix...  Turns out, I just fell asleep on the keyboard.
Most Valuable Expert 2011
Top Expert 2015
Commented:
It's rare that you want true "fire and forget" functionality in code. Most of the time you want some result back at some time. The beauty of the async bits that we have in .NET now is that you can start the async work, and then continue on with other code that doesn't rely on the result of that async work. Then, when you need the result of the async work, you either wait for it or use it directly if it's already completed.

With async/await, true "fire and forget" functionality only comes when your async methods return void (i.e. Sub, not Function). But, best practices for async code reserve void-returning async methods only for event handlers. (Well, not only for event handlers, but you better have a damn good reason for anything other than an event handler.) The reasons for which have to do with how tasks and the corresponding state machines that they get turned into function.
I guess that makes sense..we should at least track when the async work finishes.

So if I use a task variable to hold the resulting task returned from the Async function, the error message goes away. And I remade my app to track the task's status (ie. RanToCompletion, Faulted, WaitingForActivation, etc.).

This helps a lot. Thank you!

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial