我在我的Nancy应用程序中使用StackExchange.Redis
(SE.R)。只有一个全局ConnectionMultiplexer
由Nancy的TinyIoC
通过构造函数参数自动传递,并且每当我尝试使用GetDatabase
和其中一个*Async
方法时(同步)方法只在尝试了其中一个异步方法后才开始失败)我的应用程序死锁。
看看我的并行堆栈似乎有四个线程:
Result
的线程。 (堆栈中有很多Nancy东西,然后调用我的库使用SE.R,并调用Result
。堆栈顶部是Monitor.Wait
)。Native to Managed Transition
,ThreadHelper.ThreadStart
开头,堆栈顶部为ThreadHelper.ThreadStart_Context
。Monitor.Wait
Monitor.Wait
SocketManager.WriteAllQueues
SocketManager.cctor.AnonymousMethod__16
Managed to Native Transition
SocketManager.ReadImpl
SocketManager.Read
SocketManager.cctor.AnonymousMethod__19
我几乎可以肯定这是某种僵局。我甚至认为它可能与this question有关。但我不知道该怎么做。
使用以下代码在南希ConnectionMultiplexer
中设置IRegistrations
:
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
是在构造函数参数列表中请求它的所有代码共享的实例。
我有一个名为SchemaCache
的班级。它的一小部分(包括引发错误的代码)如下:
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();
}
});
}
我还有一个类在需要缓存中的一些信息的方法中使用它:
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
(后来是关于没有可用连接的例外)。唯一的区别是我通过依赖注入得到了一个缓存实例,我很确定Nancy使用Tasks来并行化请求。
答案 0 :(得分:13)
我们走了:
return Cache.UseCache1().Result
吊杆;僵局。但与StackExchange.Redis无关。
至少来自大多数同步上下文提供商。这是因为您的await
都隐式请求同步上下文激活 - 这可能意味着&#34;在UI线程&#34; (winforms,WPF),或者#34;在当前指定的工作线程上#34; (WCF,ASP.NET,MVC等)。这里的问题是此线程永远不可用于处理这些项,因为.Result
是一个同步和阻塞调用。因此,没有任何完成回调将被处理,因为唯一可以处理它们的线程正在等待它们完成,然后才能使它自己可用。
注意:StackExchange.Redis 不使用sync-context;它明确地断开与sync-context的连接,以避免成为死锁的原因(这是正常的,建议用于库)。关键是您的代码不。
选项:
async
和.Result
/ .Wait()
,或await
次调用(或至少.UseCache1()
下面的调用)明确调用.ConfigureAwait(false)
- 但是,请注意,这意味着完成将不会出现在调用上下文中!第一种选择是最简单的;如果你可以隔离一个不依赖于同步上下文的调用树,那么第二种方法是可行的。
这是一个非常普遍的问题; I did pretty much the same thing