【发布时间】:2018-10-26 07:06:23
【问题描述】:
部分作为探索异步的练习,我想尝试创建一个ServiceBrokerWatcher 类。这个想法与FileSystemWatcher 非常相似 - 监视资源并在发生某些事情时引发事件。我希望通过异步而不是实际创建线程来做到这一点,因为野兽的本质意味着大多数时候它只是在等待 SQL waitfor (receive ...) 语句。这似乎是异步的理想用法。
我编写了“有效”的代码,因为当我通过代理发送消息时,类会注意到它并触发相应的事件。我认为这非常整洁。
但我怀疑我对正在发生的事情的理解存在根本性的错误,因为当我试图阻止观察者时,它的行为并不符合我的预期。
先简要概述组件,然后是实际代码:
我有一个存储过程,它发出waitfor (receive...) 并在收到消息时将结果集返回给客户端。
有一个Dictionary<string, EventHandler> 将消息类型名称(在结果集中)映射到适当的事件处理程序。为简单起见,我在示例中只有一种消息类型。
watcher 类有一个“永远”循环的异步方法(直到请求取消),其中包含过程的执行和事件的引发。
那么,有什么问题吗?好吧,我尝试在一个简单的 winforms 应用程序中托管我的课程,当我点击按钮调用 StopListening() 方法(见下文)时,执行并没有像我想象的那样立即取消。 listener?.Wait(10000) 行实际上会等待 10 秒(或者我设置的超时时间有多长)。如果我观察 SQL 分析器发生的情况,我可以看到注意力事件正在“立即”发送,但该函数仍然没有退出。
我已将 cmets 添加到以“!”开头的代码中我怀疑我误解了什么。
所以,主要问题:为什么我的 ListenAsync 方法不“尊重”我的取消请求?
另外,我认为这个程序(大部分时间)只消耗一个线程是对的吗?我做了什么危险的事吗?
代码如下,我尽量减少它:
// class members //////////////////////
private readonly SqlConnection sqlConnection;
private CancellationTokenSource cts;
private readonly CancellationToken ct;
private Task listener;
private readonly Dictionary<string, EventHandler> map;
public void StartListening()
{
if (listener == null)
{
cts = new CancellationTokenSource();
ct = cts.Token;
// !I suspect assigning the result of the method to a Task is wrong somehow...
listener = ListenAsync(ct);
}
}
public void StopListening()
{
try
{
cts.Cancel();
listener?.Wait(10000); // !waits the whole 10 seconds for some reason
} catch (Exception) {
// trap the exception sql will raise when execution is cancelled
} finally
{
listener = null;
}
}
private async Task ListenAsync(CancellationToken ct)
{
using (SqlCommand cmd = new SqlCommand("events.dequeue_target", sqlConnection))
using (CancellationTokenRegistration ctr = ct.Register(cmd.Cancel)) // !necessary?
{
cmd.CommandTimeout = 0;
while (!ct.IsCancellationRequested)
{
var events = new List<string>();
using (var rdr = await cmd.ExecuteReaderAsync(ct))
{
while (rdr.Read())
{
events.Add(rdr.GetString(rdr.GetOrdinal("message_type_name")));
}
}
foreach (var handler in events.Join(map, e => e, m => m.Key, (e, m) => m.Value))
{
if (handler != null && !ct.IsCancellationRequested)
{
handler(this, null);
}
}
}
}
}
【问题讨论】:
-
附录:如果我启动查询探查器,我确实会在我调用
StopListening()时看到注意事件几乎立即命中 SQL Server,因此该部分似乎正在工作,但listenerTask 仍然没有完成。我觉得我可能在这里错过了一些非常明显的东西...... -
调用
ct.ThrowIfCancellationRequested()而不是在ListenAsync中检查ct.IsCancellationRequested -
谢谢科林,但我一定是做错了什么。我尝试在函数的几个不同位置(在 while 循环的开头和结尾)添加
ct.ThrowIfCancellationRequested(),但行为是相同的。
标签: c# asynchronous sqldatareader cancellationtokensource cancellation-token