我正在寻求使用StackExchange.Redis实现一个简单的分布式工作队列系统。
我理解没有BLPOP
等的原因,但是因为我正在努力的界面基于重复的TryRead
调用超时。
我对下面的内容有所了解,因为我在处理程序中取消订阅,并设置一个标志来取消超时。有没有机会可能错过?是否有不同的方法来实现这一目标?
public string TryRead(string queueName, TimeSpan timeout)
{
string result = null;
var chanName = $"qnot_{queueName}";
var done = new ManualResetEvent(false);
void Handler(RedisChannel chan, RedisValue val)
{
_sub.Unsubscribe(chanName, Handler);
result = _database.ListRightPop($"qdata_{queueName}");
done.Set();
}
_sub.Subscribe(chanName, Handler);
done.WaitOne(timeout);
return result;
}
public void Write(string queueName, string text)
{
_database.ListLeftPush($"qdata_{queueName}", text);
_sub.Publish($"qnot_{queueName}", "");
}
如果队列中存在现有项目(并且未添加任何新项目),则上述版本将始终超时并返回null
。以下版本现在首先检查现有数据,这是有效的。但它有一个错误,一个竞争条件:如果第一次读取检查返回否定,然后推送某些内容并发送通知,那么我们订阅并等待超时。
public string TryRead(string queueName, TimeSpan timeout)
{
var dataName = $"qdata_{queueName}";
var result = (string)_database.ListRightPop(dataName);
if (result != null)
{
return result;
}
var chanName = $"qnot_{queueName}";
var done = new ManualResetEvent(false);
void Handler(RedisChannel chan, RedisValue val)
{
_sub.Unsubscribe(chanName, Handler);
result = _database.ListRightPop(dataName);
done.Set();
}
_sub.Subscribe(chanName, Handler);
done.WaitOne(timeout);
return result;
}
我可以在循环中执行RPOP
,但这似乎绝对很糟糕。其他人做过类似的事情吗?
答案 0 :(得分:0)
我最终得到了这个,但是我仍然会以一种可行的方法欢迎其他答案:
public string TryRead(string queueName, TimeSpan timeout)
{
var timer = Stopwatch.StartNew();
var dataName = $"{_keyPrefix}qdata_{queueName}";
var chanName = $"{_keyPrefix}qnot_{queueName}";
var done = new AutoResetEvent(false);
string result;
// subscribe - sets the 'done' flag when a new item is pushed
void Handler(RedisChannel chan, RedisValue val)
{
done.Set();
}
_sub.Subscribe(chanName, Handler);
do
{
// try to read right away (before waiting), in case there was data already there
result = _database.ListRightPop(dataName);
if (result != null)
{
continue;
}
// there wasn't an item right away, so wait for the timeout to expire
// or the subscription to be fired. if it fired, try the read again
var remainingTime = timeout - timer.Elapsed;
if (remainingTime.TotalMilliseconds <= 1.0)
{
break;
}
if (done.WaitOne(remainingTime))
{
result = _database.ListRightPop(dataName);
}
} while (result == null && timer.Elapsed < timeout);
_sub.Unsubscribe(chanName, Handler);
return result;
}
修改:已更新w / AutoResetEvent
并已从处理程序中删除Unsubscribe
。对于那些发现这一点的人来说,这似乎对我来说是一个替代单个阻塞读取的替代品,但它不会是推荐的方法。我只是使用它,因为我希望与其他队列实现保持一致,并且正在努力使用这个特定的TryRead
签名。