为啥这个 MVC 方法没有以并行方式运行?

Posted

技术标签:

【中文标题】为啥这个 MVC 方法没有以并行方式运行?【英文标题】:Why is this MVC method not running in parallel manner?为什么这个 MVC 方法没有以并行方式运行? 【发布时间】:2020-07-05 00:49:13 【问题描述】:

背景

我有一个 MVC 5 应用程序,想测试请求是否并行运行。为此,我使用了下面的代码,并打开了多个发出相同请求的页面。

代码

下面是一个比较简单的方法,我想要并行性。

public async Task<ActionResult> Login(string returnUrl, string message = "")

    var rng = new Random();

    var wait = rng.Next(3, 10);

    var threadGuid = Guid.NewGuid();
    DebugHelper.WriteToDebugLog($"Thread threadGuid about to wait wait seconds");
    await Task.Delay(wait * 1000);
    DebugHelper.WriteToDebugLog($"Thread threadGuid finished");

    return View();

DebugHelper 类只是用来安全地写入文件。

public static class DebugHelper

    private static readonly object WriteLock = new object();

    public static void WriteToDebugLog(string message, string path = "C:\\Temp\\Log.txt")
    
        lock (WriteLock)
        
            File.AppendAllLines(path, new string[]  "", GetDateString(), message );
        
    

输出

我一直得到这种类型的输出,表明线程相互阻塞。

2020-03-24T13:43:43.1431913Z
Thread 6e42a6c5-d3cb-4541-b8aa-34b290952973 about to wait 7 seconds

2020-03-24T13:43:50.1564077Z
Thread 6e42a6c5-d3cb-4541-b8aa-34b290952973 finished

2020-03-24T13:43:50.1853278Z
Thread 90923f55-befd-4224-bdd8-b67f787839fc about to wait 4 seconds

2020-03-24T13:43:54.1943271Z
Thread 90923f55-befd-4224-bdd8-b67f787839fc finished

2020-03-24T13:43:54.2312257Z
Thread fa2d8d30-b762-4262-b188-0b34da5f4f04 about to wait 3 seconds

2020-03-24T13:43:57.2370556Z
Thread fa2d8d30-b762-4262-b188-0b34da5f4f04 finished

2020-03-24T13:43:57.2679690Z
Thread 37311a0e-d19e-4563-b92a-5e5e3def379a about to wait 8 seconds

2020-03-24T13:44:05.2812367Z
Thread 37311a0e-d19e-4563-b92a-5e5e3def379a finished

问题

为什么会这样?

我的印象是任何 ASP.NET 应用程序一开始都是多线程的,所以即使在我没有 async/await 设置的情况下,我认为它会同时运行这些线程。

更新

正如答案/cmets 中所建议的,我的方法是错误的。使用以下代码后,我可以在日志中非常清楚地看到它确实是并行运行的。

var targetTime = DateTime.UtcNow + TimeSpan.FromSeconds(5);
while(DateTime.UtcNow < targetTime)

    DebugHelper.WriteToDebugLog($"Thread threadGuid with ID threadId doing stuff");
    await Task.Delay(1000);

【问题讨论】:

关于变量threadGuid的名称,你确定你的代码在await Task.Delay之后的同一个线程中运行吗?您也可以通过记录Thread.CurrentThread.ManagedThreadId 来测试它。 请不要用日志重新发明***——你可能会弄错,就像你在这里用你的 WriteLock 做的那样。查看像 Serilog 和 NLog 这样的优秀库。 【参考方案1】:

归结为这样一个事实,即您使用其WriteLock 和同步File.AppendAllLines 的调试日志会强制对所有调用它的异步函数进行同步锁定。

您最好有一个异步写入调试过程,这将允许您的任务继续运行。

产品/消费者模式、信号量、事件、异步文件访问 API 的使用都浮现在脑海中。

【讨论】:

最近发布的频道 API 是构建生产者/消费者工作负载的绝佳方式。它很简单,而且性能很好。【参考方案2】:

如果您完全使用会话,它可以将用户锁定到单个线程。检查控制器级别、页面级别或过滤器/属性会话使用情况。如果不确定,请尝试添加

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]

到控制器。

此外,默认情况下,await 将在开始 await 的同一线程上继续。尝试使用 configureAwait(false) 让它在它使用的线程中灵活。

await Task.Delay(wait * 1000).ConfigureAwait(false);

【讨论】:

您不应在 ASP.NET 应用程序中使用 .ConfigureAwait(false)。见guidance on its usage。 非常非常了解这些准则。自 Tasks 发布以来,我一直在使用它们,并编写了大量复杂的多线程异步应用程序。他们只是声明,因为大多数开发人员不太了解上下文并且倾向于忘记他们需要在哪个线程上。如果您不需要线程上下文,它ISN'T 有害。

以上是关于为啥这个 MVC 方法没有以并行方式运行?的主要内容,如果未能解决你的问题,请参考以下文章

Elixir:应用程序立即退出,或者在运行 distillery 包时没有收到输入。为啥它会以这种方式工作,以及如何解决它?

android studio安装完为啥在桌面没有快捷方式

为啥我的 Visual Studio 开始以这种方式运行? iisexpress 找不到或打开 PDB 文件

为啥 IE 会以不同的方式呈现这个 HTML 表格?

MVC 5 以同步方式使用 Identity 2 创建用户

async/await 如何以串行和并行方式工作?