Reputation: 87
Background: I have a long linq query to database in my web-app. So I want to show a user a query-timer. I tried to do this through async/await statements, but I stuck. (I use signalR for real-time mode)
A peace of my test-code:
MyHub.cs:
public class MyHub : Hub
{
public void Message(string message)
{
Clients.All.message(message);
}
public void Tick(int value)
{
Thread.Sleep(1000);
Clients.Caller.tick(value);
}
public void StartAsyncMethod(int value)
{
Clients.All.message("<br> M1: StartAsyncMethod started!");
int t = 0;
MyAsyncClass myAsyncClass = new MyAsyncClass();
Task task = myAsyncClass.AsyncMethod(value*1000);
Clients.All.message("<br> M1: Start While...");
while (!task.IsCompleted)
{
t++;
Tick(t);
}
Clients.All.message("<br> M1: StartAsyncMethod Finished!");
}
}
MyAsyncClass.cs
public class MyAsyncClass
{
public async Task AsyncMethod(int _i)
{
var hub = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
hub.Clients.All.message($"<br>==M2== Wait for {_i} milliseconds");
await Task.Run(() => Sleep(_i));
hub.Clients.All.message($"<br>==M2== Finish inner method...");
}
private void Sleep(int value)
{
Thread.Sleep(value);
}
}
And the point is in while (!task.IsCompleted)
- the app stacks on that state:
If while
is commented, it works fine (but without timer of course):
More than that - in simple console application this code works in both variants!
Code:
class Program
{
static void Main(string[] args)
{
int i = 5;
Console.WriteLine($"Main : Start StartAsyncMethod({i})");
StartAsyncMethod(i);
Console.WriteLine($"Main : FINISH StartAsyncMethod({i})");
Console.WriteLine("\n\nPress any key to exit....");
Console.ReadKey();
}
public static void StartAsyncMethod(int value)
{
Console.WriteLine(" M1 : StartAsyncMethod started!");
int timer = 0;
MyAsyncClass myAsyncClass = new MyAsyncClass();
Task task = myAsyncClass.AsyncMethod(value * 1000);
//while (!task.IsCompleted)
//{
// timer++;
// Thread.Sleep(1000);
// Console.WriteLine(" M1 : \"hello\" from While cicle ... ");
//}
Console.WriteLine(" M1 : StartAsyncMethod FINISHED!");
}
}
public class MyAsyncClass
{
public async Task AsyncMethod(int _i)
{
Console.WriteLine($" M2 : Wait for {_i} milliseconds");
await Task.Run(() => Sleep(_i));
Console.WriteLine($" M2 : Finish inner method...");
}
private void Sleep(int v)
{
Console.WriteLine($" M2 : Sleep for {v} mls");
Thread.Sleep(v);
}
}
Outputs:
With while
:
Without while
:
Question: does this code really works in different ways in ASPnet and ConsoleApp, or I have missed something?
Thanks.
Upvotes: 1
Views: 1333
Reputation: 1994
That's because you're misusing async/await
. Note, that you have 2 tasks there: one returned by Task.Run
and another returned by AsyncMethod
. Looping on the top-level Task
you're blocking the request thread and continuation of Task.Run
which is
hub.Clients.All.message($"<br>==M2== Finish inner method...");
cannot be executed in ASP.NET because it uses synchronization context which in case if ASP.NET ensures executing no more than one thread a time for threads which share the synchronization context (a request thread and continuation threads share the context). As such the second task cannot finish as well. Console doesn't use synchronization context so your continuations are scheduled on thread pool.
You need either use ConfigureAwait(flase)
with scheduling of your async tasks, or do everything asynchronously (which is more proper way). You can see an example here of how to implement progress for async
tasks.
Update: For sake of giving some clarity of synchronization context purpose. Let's imagine typical asynchronous flow winch is triggered by an asynchronous event (an user input, incoming request, etc.) and finishes with some asynchronous action (pushing data to an external data source, file, pulling data from a web resource, etc.). An asynchronous application deals with many of such flows, which, because of their asynchronous nature, can start concurrently and can finish concurrently and this fact among of the benefits imposes some pitfalls.
For example, if a flow invokes multiple asynchronous operations the concurrency of their continuations might be a problem. The solution is synchronization of continuations - that's where a synchronization context comes to play. So in general it represents some abstraction to schedule ordered execution by means of two methods 'Send' and 'Post' which have semantic of "invoke and wait" and "fire and forget" respectively.
Why not just synchronization primitives ? Briefly synchronization context provides more generic approach in half-sync/half-async like pattern way. Often the resources used to serve asynchronous continuations are quite expensive (for example on Windows family OS the I/O completion ports mechanism implies using special thread pool for serving completed I/O requests) and it's highly recommended not to occupy such resources for longer than needed period of time so "fire and forget" approach is often a preferable way instead of waiting on a synchronization object and blocking a thread which would serve another asynchronous continuations and synchronization context provides an abstraction which allows to utilize the underlying infrastructure efficiently.
As a side effect of some synchronization context implementations the possibility to delegate execution of some code from one thread to another specific thread can be highlighted (like WinForms or WPF synchronization contexts do ) but I'd say it's rather implementation specific thing.
Upvotes: 2
Reputation: 2631
In addition to @Dmytro Mukalov answer I will try to answer your question:
does this code really works in different ways in ASPnet and ConsoleApp, or I have missed something?
Yes, most definitely!
Think about it: the web client only gets updated in One shot through the web requests. It is indirectly related to the threads on the server.
In the console app the output messages gets produced immediately on the console, while on the ASP.net MVC the the results/messages get assembled in Await (time dependent) fashion and then sent back to client.
In short, Coding TAP pattern in ASP.net doesn't translate directly from console apps. For a more thorough explanation please read:
Async Programming : Introduction to Async/Await on ASP.NET
Upvotes: 1