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
来确保它不会被大量并行化。
代码中的主要问题是代码不是异步的!
对于您创建的每个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
的好文章,所以你可以试着从那里开始。