StackExchange.Redis死锁 [英] StackExchange.Redis Deadlocking
问题描述
我用 StackExchange.Redis
(SE.R今后)在我的南希的应用程序。有一个单一的全球 ConnectionMultiplexer
这就会自动围绕由南希的 TinyIoC
通过构造函数参数传递,任何时候我尝试使用 GetDatabase
和 *异步
方法(同步方法只启动失败的异步方法之一已经尝试后的一个)我的应用程序死锁。
I'm using StackExchange.Redis
(SE.R henceforth) in my Nancy application. There is a single global ConnectionMultiplexer
that gets automatically passed around by Nancy's TinyIoC
via constructor parameters, and any time I try and use GetDatabase
and one of the *Async
methods (sync methods only start failing after one of the async methods have been attempted) my application deadlocks.
看着我的平行叠层看来我有四个主题:
Looking at my parallel stacks it appears that I have four threads:
- 调用线程
结果
我的任务之一,它使用SE.R. (有很多的东西,南希在堆栈上,然后我的图书馆,利用SE.R一个电话,为结果
顶部堆叠的是<$ C $通话C> Monitor.Wait )。 - 这催生了另外两个线程的线程。我认为这是由SE.R.管理以管理过渡,
ThreadHelper.ThreadStart
,并在堆栈的顶部是本机启动> ThreadHelper.ThreadStart_Context
。 - 被卡住像这样的小堆栈:
-
Monitor.Wait
-
Monitor.Wait
-
SocketManager.WriteAllQueues
-
SocketManager.cctor.AnonymousMethod__16
-
- The thread that called
Result
on one of my Tasks that uses SE.R. (Has lots of Nancy stuff on the stack, then a call to my library that utilizes SE.R, and a call toResult
. Top of the stack isMonitor.Wait
). - A thread that spawned two other threads. I assume this is managed by SE.R. Starts with
Native to Managed Transition
,ThreadHelper.ThreadStart
, and at the top of the stack isThreadHelper.ThreadStart_Context
. - A small stack that is stuck like this:
Monitor.Wait
Monitor.Wait
SocketManager.WriteAllQueues
SocketManager.cctor.AnonymousMethod__16
-
托管到本机过渡
-
SocketManager.ReadImpl
-
SocketManager.Read
-
SocketManager.cctor.AnonymousMethod__19
Managed to Native Transition
SocketManager.ReadImpl
SocketManager.Read
SocketManager.cctor.AnonymousMethod__19
我几乎可以肯定这是某种形式的僵局。我甚至认为它可能是与<一个href=\"http://stackoverflow.com/questions/25567566/timeout-exception-after-async-commands-and-task-whenany-awaits-in-stackexchange?rq=1\">this问题。但我不知道该怎么办才好。
I'm almost sure this is a deadlock of some sort. I even think it might have something to do with this question. But I have no idea what to do about it.
的 ConnectionMultiplexer
设置在南希 IRegistrations
通过以下code:
The ConnectionMultiplexer
is set up in a Nancy IRegistrations
with the following code:
var configOpts = new ConfigurationOptions {
EndPoints = {
RedisHost,
},
Password = RedisPass,
AllowAdmin = false,
ClientName = ApplicationName,
ConnectTimeout = 10000,
SyncTimeout = 5000,
};
var mux = ConnectionMultiplexer.Connect(configOpts);
yield return new InstanceRegistration(typeof (ConnectionMultiplexer), mux);
MUX
是获取所有code共享实例,要求它在其构造参数列表。
mux
is the instance that gets shared by all code requesting it in their constructor parameter list.
我有一类称为 SchemaCache
。一小块它(包含code抛出错误的问题)如下:
I have a class called SchemaCache
. A small piece of it (that includes the code that throws the error in question) follows:
public SchemaCache(ConnectionMultiplexer connectionMultiplexer) {
ConnectionMultiplexer = connectionMultiplexer;
}
private ConnectionMultiplexer ConnectionMultiplexer { get; set; }
private async Task<string[]> Cached(string key, bool forceFetch, Func<string[]> fetch) {
var db = ConnectionMultiplexer.GetDatabase();
return forceFetch || !await db.KeyExistsAsync(key)
? await CacheSetSet(db, key, await Task.Run(fetch))
: await CacheGetSet(db, key);
}
private static async Task<string[]> CacheSetSet(IDatabaseAsync db, string key, string[] values) {
await db.KeyDeleteAsync(key);
await db.SetAddAsync(key, EmptyCacheSentinel);
var keysSaved = values
.Append(EmptyCacheSentinel)
.Select(val => db.SetAddAsync(key, val))
.ToArray()
.Append(db.KeyExpireAsync(key, TimeSpan.FromDays(1)));
await Task.WhenAll(keysSaved);
return values;
}
private static async Task<string[]> CacheGetSet(IDatabaseAsync db, string key) {
var results = await db.SetMembersAsync(key);
return results.Select(rv => (string) rv).Without(EmptyCacheSentinel).ToArray();
}
// There are a bunch of these public methods:
public async Task<IEnumerable<string>> UseCache1(bool forceFetch = false) {
return await Cached("the_key_i_want", forceFetch, () => {
using (var cnn = MakeConnectionToDatabase("server", "databaseName")) {
// Uses Dapper:
return cnn.Query<string>("--expensive sql query").ToArray();
}
});
}
我也有一个类,使得这一用途的方法需要一些从缓存中的信息:
I also have a class that makes uses of this in a method requiring some of the information from the cache:
public OtherClass(SchemaCache cache) {
Cache = cache;
}
private SchemaCache Cache { get; set; }
public Result GetResult(Parameter parameter) {
return Cache.UseCache1().Result
.Where(r => Cache.UseCache2(r).Result.Contains(parameter))
.Select(r => CheckResult(r))
.FirstOrDefault(x => x != null);
}
所有细上述作品LinqPad那里的问题都只有一个实例。相反,它失败了 TimeoutException异常
(后来的关于无可用的连接除外)。唯一的区别是,我通过依赖注入获得高速缓存的实例,我pretty确保南希使用任务并行的要求。
All of the above works fine in LinqPad where there's only one instance of everything in question. Instead it fails with a TimeoutException
(and later an exception about no available connection). The only difference is that I get an instance of the cache via dependency injection, and I'm pretty sure Nancy uses Tasks to parallelize the requests.
推荐答案
在这里,我们去:
return Cache.UseCache1().Result
热潮;僵局。但无关StackExchange.Redis。
boom; deadlock. But nothing to do with StackExchange.Redis.
至少,从最同步上下文提供者。这是因为你的等待
s的所有隐含要求同步上下文激活 - 这可能意味着在UI线程(的WinForms,WPF),或者在当前指定的工线(WCF,ASP.NET,MVC等)。这里的问题是,此线程将永远不会可用来处理这些项,因为。结果
是同步和阻塞调用。因此,你没有完成回调将得到处理,因为那都不可能处理它们唯一的线程在等待他们使自己可用之前完成。
At least, from most sync-context providers. This is because your await
s are all implicitly requesting sync-context activation - which can mean "on the UI thread" (winforms, WPF), or "on the currently designated worker thread" (WCF, ASP.NET, MVC, etc). The problem here is that this thread will never be available to process those items, because .Result
is a synchronous and blocking call. Thus none of your completion callbacks will get processed, because the only thread that can possibly process them is waiting for them to finish before making itself available.
请注意:StackExchange.Redis的不使用同步上下文;它明确地同步上下文断开,以避免被死锁的原因(这是正常的,建议库)。关键的一点是,你的code的不的
Note: StackExchange.Redis does not use the sync-context; it explicitly disconnects from sync-context to avoid being the cause of deadlocks (this is normal and recommended for libraries). The key point is that your code doesn't.
选项:
- 请不要混用
异步
和。结果
/.Wait()
或 - 有你的
等待
电话(或至少是下面.UseCache1()
)显式调用<$的C $ C> .ConfigureAwait(假)! - 但是请注意,这意味着落成将不会出现在调用上下文
- don't mix
async
and.Result
/.Wait()
, or - have all of your
await
calls (or at least those underneath.UseCache1()
) explicitly call.ConfigureAwait(false)
- note, however, that this means that the completions will not occur in the calling context!
第一种选择是简单的;如果可以隔离呼叫的树不依赖于同步上下文那么第二种方法是可行的。
The first option is the simplest; if you can isolate a tree of calls that do not depend on sync-context then the second approach is viable.
这是一个非常普遍的问题; <一href=\"http://stackoverflow.com/questions/13621647/using-async-even-if-it-should-complete-as-part-of-a-mvc-route-deadlocks-the\">I做pretty同样的事情。
This is a very common problem; I did pretty much the same thing.
这篇关于StackExchange.Redis死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!