背景:我的网络应用中有一个很长的linq查询到数据库。因此,我想向用户显示一个查询计时器。我试图通过async / await语句来做到这一点,但是我坚持了下来。 (我将signalR用于实时模式)
我的测试代码很安全
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);
}
}
关键点是while (!task.IsCompleted)
-应用程序在该状态下堆叠:
如果对while
进行注释,则可以正常运行(但当然没有计时器):
更多-在简单的控制台应用程序中,此代码在两种变体中均有效!
代码:
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);
}
}
输出:
使用while
:
没有while
:
问题:该代码在ASPnet和ConsoleApp中是否真的以不同的方式工作,还是我错过了什么?
谢谢。
答案 0 :(得分:2)
那是因为您滥用async/await
。请注意,您在那里有2个任务:一个任务由Task.Run
返回,另一个任务由AsyncMethod
返回。在顶级Task
上循环,您将阻止请求线程并继续Task.Run
hub.Clients.All.message($"<br>==M2== Finish inner method...");
不能在ASP.NET中执行,因为它使用了同步上下文,如果ASP.NET确保一次执行不超过一个线程的共享同步上下文的线程(请求线程和延续线程共享上下文),则
不能在ASP.NET中执行。因此,第二项任务也无法完成。控制台不使用同步上下文,因此您的继续安排在线程池中进行。
您需要使用ConfigureAwait(flase)
来安排异步任务的时间,或者异步执行所有操作(这是更合适的方法)。您可以看到here的示例,了解如何为async
个任务实现进度。
更新:为了使同步上下文的目的更加清晰。让我们想象一下典型的异步流绞盘是由异步事件(用户输入,传入请求等)触发的,并以一些异步操作(将数据推送到外部数据源,文件,从Web资源中提取数据等)结束。 。异步应用程序处理许多这样的流,由于它们的异步特性,它们可以同时启动并可以同时完成,而其中的这一优点带来了一些陷阱。例如,如果流调用多个异步操作,则其连续性的并发性可能会成为问题。解决方案是连续性的同步-这是同步上下文发挥作用的地方。因此,通常,它代表某种抽象方法,可以通过两种方法“发送”和“发布”来调度有序执行,这两种方法的语义分别为“调用并等待”和“触发并忘记”。为什么不只是同步原语?简而言之,同步上下文以半同步/半异步之类的方式提供了更通用的方法。通常,用于服务异步继续的资源非常昂贵(例如,在Windows家族操作系统上,I / O完成端口机制意味着使用特殊的线程池来服务已完成的I / O请求),强烈建议不要将此类资源占用的时间长于所需的时间,因此“解雇”方法通常是一种更好的方法,而不是等待同步对象并阻塞将为另一个异步继续提供服务的线程,并且同步上下文提供了一种允许有效利用基础结构的抽象。作为某些同步上下文实现的副作用,可以强调将某些代码从一个线程执行委托给另一个特定线程的可能性(就像WinForms或WPF同步上下文一样),但我想这是实现特定的事情。
答案 1 :(得分:1)
除了@Dmytro Mukalov回答,我还将尝试回答您的问题:
此代码确实在ASPnet和 ConsoleApp,还是我错过了什么?
是的,绝对可以!
考虑一下:Web客户端只能通过Web请求一次更新。它与服务器上的线程间接相关。
在控制台应用程序中,输出消息立即在控制台上生成,而在ASP.net MVC上,结果/消息以Await(与时间有关)的方式组装,然后发送回客户端。
简而言之,ASP.net中的编码TAP模式不会直接从控制台应用程序转换。有关更详尽的解释,请阅读: