MongoDB 2.0驱动程序和非异步代码的潜在死锁

本文关键字:代码 死锁 异步 驱动程序 MongoDB | 更新日期: 2023-09-27 18:21:24

我们有一个ASP.NET MVC网站,并将所有文本存储在MongoDB中。类LocalizationTextManager负责提供这些文本并在内部缓存它们。通常,这种方法非常快(<5ms),如果结果在缓存中,则速度甚至更快。

我们有两个方法:GetString和GetStringAsync。GetStringAsync是首选,但我们在Razor中使用GetString方法,例如,或者在一些不在异步上下文中的罕见情况下使用。

MongoDB有一个异步驱动程序,我需要非同步地实现它。因此,我们尝试了几种方法。我确保在代码中的任何位置都设置了ConfigureAwait(false)。

FindOrAddTextFromRepositoryAsync(key).Result;
Task.Run(async () => await FindOrAddTextFromRepositoryAsync(key)).Result;
Task.Run(async () => await FindOrAddTextFromRepositoryAsync(key).ConfigureAwait(false)).Result;

我知道我在任务中不需要ConfigureAwait(false)(因为不应该有同步上下文)。

我刚刚部署了网站,部署后它就挂起了。经过几次重新启动过程后,它开始工作。我以前做过转储,发现有很多这样的方法调用:

w3wp(4).DMP中的以下线程正在System.Threading.Monitor.Wait.~100中等待线程被阻止:

mscorlib_ni!System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)+3ec 
mscorlib_ni!System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken)+db 
mscorlib_ni!System.Threading.Tasks.Task.InternalWait(Int32, System.Threading.CancellationToken)+24a 
mscorlib_ni!System.Threading.Tasks.Task`1[[System.__Canon, mscorlib]].GetResultCore(Boolean)+36 
GP.Components.Globalization.LocalizationTextManager.GetString(System.String, System.String)+2f4 
GP.Components.Globalization.LocalizationTextManager.GetString(System.String, System.Globalization.CultureInfo)+8a 

我的问题是:如何正确地实现它?另一个想法是使用LimitedThreadsScheduler来确保它不会被大量并行化。

MongoDB 2.0驱动程序和非异步代码的潜在死锁

代码中的主要问题是代码不是异步的!

对于您创建的每个Task,您显式地调用Result属性

.Result;

这导致阻塞当前线程直到任务完成。

如果需要处理Task.Complete event,可以使用Task类的延续方法或静态方法来等待任务挂起。简单地不要阻止你的任务:

.ContinueWith( (t) => { Console.WriteLine(t.Result); },
    TaskContinuationOptions.OnlyOnRanToCompletion);

或:

Task.WaitAll(tasks);

正如我所看到的,在跟踪GetString中,非异步版本正在运行并等待结果,因此其他线程无法执行任何操作。我建议您尝试通过为Tasks使用的默认线程池设置MaximumThreads来调整性能,并为不同的任务调度程序拆分同步和异步代码,这样它们就不会相互阻塞。其他任务选项在此开始解释:Task.Run vs Task.Factory.StartNew

至于你最后的问题,这里有一篇关于How to: Create a Task Scheduler That Limits Concurrency的好文章,所以你可以试着从那里开始。