Reputation: 760
I was trying out the Event based asynchronous example described at: https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/component-that-supports-the-event-based-asynchronous-pattern.
The PrimeNumberCalculator
class I wrote is exactly as described in the article. And I wrote a simple client console app to call the Calculate method as follows:
//------------CONSOLE APP MAIN------------
static void Main(string[] args)
{
CalculatePrimeNumbers();
Console.WriteLine("Application will exit here. Press a key");
Console.ReadKey();
}
//------------CALLING CODE-----------
static void CalculatePrimeNumbers()
{
PrimeNumberCalculator primeNumberCalculator = new PrimeNumberCalculator();
//setup event handlers
primeNumberCalculator.CalculatePrimeCompleted +=
new CalculatePrimeCompletedEventHandler(primeNumberCalculator_CalculatePrimeCompleted);
primeNumberCalculator.ProgressChanged +=
new ProgressChangedEventHandler(primeNumberCalculator_ProgressChanged);
for (int i = 0; i < 100; i++)
{
Random rand = new Random();
int testNumber = rand.Next(200);
Thread.Sleep(20);
Guid taskId = Guid.NewGuid();
Console.WriteLine($"{taskId} - Starting for {testNumber}");
primeNumberCalculator.CalculatePrimeAsync(testNumber, taskId);
}
}
//------------CALCULATION COMPLETED EVENT HANDLER------------
static private void primeNumberCalculator_CalculatePrimeCompleted(
object sender, CalculatePrimeCompletedEventArgs e)
{
//await Task.Yield();
Guid taskId = (Guid)e.UserState;
Console.WriteLine($"{taskId} - Whole process completed");
}
//------------CALCULATION PROGRESSED EVENT HANDLER------------
static private void primeNumberCalculator_ProgressChanged(ProgressChangedEventArgs e)
{
Guid taskId = (Guid)e.UserState;
Console.WriteLine($"{taskId} - {e.ProgressPercentage}%");
DoSomethingImportant(taskId);
}
//------------POST PROCESSING WORK------------
static void DoSomethingImportant(Guid guid)
{
//necessary Work to be done on completion/progress;
}
When I run this, the code works as it is supposed to, but after handling a whole lot of progressed and completed events, a few of events get handled after the application would have exited at the line Console.WriteLine("Application will exit here. Press a key");
3d744ab4-6d2c-4949-a1ec-ce22718daa1f - Starting for 169
d73dc2c4-a93b-49a2-8608-b2d180884295 - 69%
d73dc2c4-a93b-49a2-8608-b2d180884295 - 77%
d73dc2c4-a93b-49a2-8608-b2d180884295 - 80%
2ae4ab62-1479-4360-8e35-fdf747ed3f93 - 8%
f48a77d5-e349-4ff5-889d-c019c759a527 - Starting for 34
Application will exit here. Press a key
2ae4ab62-1479-4360-8e35-fdf747ed3f93 - 78%
f48a77d5-e349-4ff5-889d-c019c759a527 - 50%
f48a77d5-e349-4ff5-889d-c019c759a527 - 55%
f48a77d5-e349-4ff5-889d-c019c759a527 - 67%
f48a77d5-e349-4ff5-889d-c019c759a527 - 85%
f48a77d5-e349-4ff5-889d-c019c759a527 - 91%
f48a77d5-e349-4ff5-889d-c019c759a527 - Whole process completed
f48a77d5-e349-4ff5-889d-c019c759a527 - 14%
2ae4ab62-1479-4360-8e35-fdf747ed3f93 - 13%
d73dc2c4-a93b-49a2-8608-b2d180884295 - 4%
2ae4ab62-1479-4360-8e35-fdf747ed3f93 - 15%
f48a77d5-e349-4ff5-889d-c019c759a527 - 38%
f48a77d5-e349-4ff5-889d-c019c759a527 - 20%
f48a77d5-e349-4ff5-889d-c019c759a527 - 32%
In the output above, there are several event getting handled after the "Application will exit here. Press a key"
line, which would effectively skip any post processing to be done by the event handlers for those tasks.
Is this expected or am I missing something in the handlers? One of the ways that comes to my mind to avoid this is to keep track of the number of calculations (tasks) that are in flight, check them off when the "completed" event handler is raised, and hold on to the application until all in-flight calculations have been checked-off as being completed.
Is there a recommended approach to make sure all events get handled before the application exits?
Upvotes: 0
Views: 75
Reputation: 40928
The event-based asynchronous programming pattern relies on events to tell you when the job has completed. There is nothing in your Main
code that tells it to wait until all of the completion events have fired.
You could use a CountdownEvent
to do that. Start the count at 100:
static CountdownEvent cde = new CountdownEvent(100);
Subtract one in primeNumberCalculator_CalculatePrimeCompleted
by calling Signal()
:
cde.Signal();
Then wait until the count is 0 in your Main
method:
cde.Wait();
The other option is to use the Task-based asynchronous pattern instead. Then you could keep a list of all the tasks and use Task.WhenAll
to asynchronously wait for them. This article can help with that too: Asynchronous programming with async and await.
Upvotes: 3