Отчасти в качестве упражнения по изучению асинхронности я бы попытался создать класс ServiceBrokerWatcher
. Идея почти такая же, как у FileSystemWatcher
— наблюдайте за ресурсом и вызывайте событие, когда что-то происходит. Я надеялся сделать это с помощью async, а не фактически создавать поток, потому что природа зверя означает, что большую часть времени он просто ожидает оператора SQL waitfor (receive ...)
. Это казалось идеальным использованием асинхронности.
Я написал код, который «работает», когда я отправляю сообщение через брокера, класс замечает это и запускает соответствующее событие. Я думал, что это было супер аккуратно.
Но я подозреваю, что где-то в корне ошибся в своем понимании того, что происходит, потому что, когда я пытаюсь остановить наблюдателя, он ведет себя не так, как я ожидал.
Сначала краткий обзор компонентов, а затем собственно код:
У меня есть хранимая процедура, которая выдает waitfor (receive...)
и возвращает набор результатов клиенту при получении сообщения.
Существует Dictionary<string, EventHandler>
, который сопоставляет имена типов сообщений (в результирующем наборе) с соответствующим обработчиком событий. Для простоты у меня есть только один тип сообщения в примере.
Класс наблюдателя имеет асинхронный метод, который зацикливается «навсегда» (до тех пор, пока не будет запрошена отмена), который содержит выполнение процедуры и инициирование событий.
Так в чем проблема? Что ж, я попытался разместить свой класс в простом приложении winforms, и когда я нажимаю кнопку для вызова метода StopListening()
(см. ниже), выполнение не отменяется сразу, как я думал. Строка listener?.Wait(10000)
на самом деле будет ждать 10 секунд (или столько, сколько я установил тайм-аут). Если я посмотрю, что происходит с профилировщиком SQL, я увижу, что событие внимания отправляется «сразу же», но функция все равно не завершается.
Я добавил комментарии к коду, начинающиеся с "!" где я подозреваю, что я что-то неправильно понял.
Итак, главный вопрос: почему мой метод 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()
, так что эта часть, кажется, работает, ноlistener
Task все еще не завершается. Я чувствую, что мог пропустить что-то совершенно очевидное здесь... - person allmhuran   schedule 26.10.2018ct.ThrowIfCancellationRequested()
вместо проверкиct.IsCancellationRequested
вListenAsync
- person Collin Dauphinee   schedule 26.10.2018ct.ThrowIfCancellationRequested()
в несколько разных мест функции (в начале и в конце цикла while), но поведение было таким же. - person allmhuran   schedule 26.10.2018