#c# #uwp #async-await #cancellationtokensource #cancellation-token
#c# #uwp #async-await #cancellationtokensource #отмена-токен
Вопрос:
C # Проект Windows UWP
Я реализовал CancellationTokenSource и CancellationToken в асинхронном методе, который вызывает другой метод. Этот метод содержит цикл while, который поддерживает значение переменной bool как true до тех пор, пока источник токена не будет отменен.
Метод async запускается событием нажатия левой кнопки мыши и отменяется событием отпускания левой кнопки мыши, возникающим при использовании элемента управления ColorPicker. Переменная bool позволяет отправлять значения цвета на устройство при значении true и предотвращает его при значении false.
Поддерживая значение как true, устройство непрерывно получает изменяющиеся значения цвета, пока указатель перемещается вокруг средства выбора цвета, пока кнопка мыши остается нажатой. Как только кнопка мыши отпущена, результирующее значение false (которое устанавливается подпрограммой, которая отправляет значения цвета на устройство) предотвращает отправку дальнейших цветовых сообщений на устройство.
Мой код тоже делает то, что я хочу, но я обеспокоен потенциальными побочными эффектами, которые могут возникнуть, если я не реализовал его правильно. Я видел по крайней мере одно сообщение на этом форуме, которое указывает, что последовательность: cancellation, dispose и set to null может использоваться для CancellationTokenSource . Но меня беспокоит то, что у меня есть потенциально бесконечный цикл while, который полностью зависит от получения токена отмены. Итак, мой вопрос заключается в том, может ли удаление CancellationTokenSource слишком рано предотвратить токен.IsCanellationRequested не имеет значения true, и если да, то добавит ли добавление задержки какую-либо выгоду?
Ниже приведены соответствующие фрагменты из моего кода:
Глобальные переменные:
public static bool colorPickerPtrPressed = false;
static CancellationTokenSource cts = null;
static CancellationToken token;
События кнопки мыши:
private void ColorPicker_PtrPressedEvent(object sender, PointerRoutedEventArgs e)
{
if(cts == null) cts = new CancellationTokenSource();
token = cts.Token;
var picker = sender as ColorPicker.ColorPicker;
colorPickerPtrPressed = true;
(picker.DataContext as SpheroViewModel).Color = picker.SelectedColor.Color;
ColorChange(token);
}
private void ColorPicker_PtrReleasedEvent(object sender, PointerRoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
Task.Delay(500).Wait(); // Allow some time for cancel to take effect
cts.Dispose();
cts = null;
}
}
Способы отмены токена:
public static async Task ColorChange(CancellationToken token)
{
await Task.Run(() =>
AllowColorChange(token), token);
}
public static void AllowColorChange(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
colorPickerPtrPressed = true; // Maintain value as true
Task.Delay(100).Wait(); // allow color updates periodically
}
return;
}
Ответ №1:
Итак, мой вопрос заключается в том, будет ли задержка между отменой и удалением CancellationTokenSource, как я сделал в «colorpicker_prreleasedevent» ниже, обеспечит ли какую-либо гарантию того, что цикл while завершится?
Нет. Модель отмены является кооперативной, и вызов Cancel
просто предоставляет уведомление об отмене, устанавливая IsCancellationRequested
свойство всех токенов true
равным .
Затем любой отменяемый API, то есть любой метод, который принимает a CancellationToken
, должен отслеживать значение этого свойства и отвечать на запрос отмены.
So ColorPicker_PtrReleasedEvent
не может гарантировать, что while
цикл завершится AllowColorChange
.
Комментарии:
1. Вы говорите, что cts. Cancel() всегда будет устанавливать токен. IsCancellationRequested имеет значение true (которое отслеживается циклом while) и что задержка после этого ничего не добавляет?
2. @Stroker347 если вы хотите убедиться, что операция отмены действительно была завершена в отмененном состоянии, ничто не заменит ожидание или (предпочтительно) ожидание этой операции после того, как вы
Cancel
укажете источник отмены. Использование произвольных задержек — это рецепт плохой производительности и спорадических сбоев приложений.3. Вы имеете в виду «ожидание cts. Cancel()» или «ожидает разрешения на изменение цвета (токена)»?
4. @Stroker347
await AllowColorChange(token)
. Или, что еще лучшеawait _task
, где_task
находится кэшированный результатAllowColorChange(token)
более раннего вызова. Который должен быть асинхронным методом, возвращающим aTask
, и иметьAsync
суффикс в соответствии с рекомендациями . И лучшее имя в целом.AllowColorChange
является ли имя подходящим дляbool
свойства, а не для метода!5. На самом деле, я бы знал, если бы цикл while не завершился, потому что изменения цвета продолжали бы отправляться на мое устройство после отпускания кнопки мыши. Однако, если бы я дождался этого и проверил завершение, я мог бы затем повторно выполнить команду Cancel(), если она не была отменена при отпускании кнопки мыши. Тестирование показало, что цикл while завершается по желанию даже без каких-либо задержек, так что, возможно, я зря беспокоюсь.
Ответ №2:
В соответствии с рекомендациями TZ я изменил последние три метода, чтобы ожидать отмененного флага от метода AllowColorChange(token), а затем использовал этот результат как разрешающий для dispose() CancellationTokenResult и установил для него значение null. Если кто-нибудь увидит проблему с тем, что я сделал, пожалуйста, дайте мне знать. Ниже приведен измененный код, который, по-видимому, работает хорошо:
private void ColorPicker_PntrReleasedEvent(object sender, PointerRoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
//Task.Delay(200).Wait(); // Allow some time for cancel to take effect
//cts.Dispose();
//cts = null;
}
}
public static async Task ColorChange(CancellationToken token)
{
bool task = false;
task = await Task.Run<bool>(() =>
AllowColorChange(token), token);
if (task)
{
cts.Dispose();
cts = null;
}
else
{
// Shouldn't ever reach this point
bool isBrkPnt = true;
}
}
public static async Task<bool> AllowColorChange(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
colorPickerPtrPressed = true; // Maintain value as true
await Task.Delay(100); // allow color updates periodically
}
return true; // signal that task was canceled
}
}
Комментарии:
1. Вы уверены, что команды
cts.Dispose(); cts = null;
удалят отмененныйcts
, а не новыйcts
, созданныйColorPicker_PtrPressedEvent
обработчиком? Кроме того, вы не передаетеtoken
Task.Delay
методу, поэтому отмена будет немного отложена (в среднем на ~ 50 мс). Кроме того, стандартная практика в случае отмены заключается в распространенииOperationCanceledException
значения, а неbool
значения. Это означает, чтоIsCancellationRequested
свойство следует использовать редко, иThrowIfCancellationRequested
вместо этого следует использовать метод.2. Я создаю новый CancellationTokenSource только в том случае, если текущий имеет значение null, поэтому их никогда не должно быть больше одного за раз, поэтому я думаю, что с первым пунктом все в порядке. Я попытался передать токен методу Delay, и это привело к тому, что мой цикл while перестал функционировать так, как я хочу, поэтому мне придется исследовать это дальше. Значение bool было естественным выбором для того, как я использую возвращаемое значение, но я рассмотрю то, что вы предложили в качестве лучшего выбора. Спасибо за ваши предложения, они были очень полезны, и я больше не беспокоюсь, что могу преждевременно избавиться от CancellationTokenSource.
3. TZ, я изменил тело задачи ColorChange(токен) на: «попробуйте{ await Task.Run(async() => {while(true){токен. ThrowIfCancellationRequested(); colorPickerPtrPressed = true; ожидание задачи. Задержка (100, токен);},токен}; } перехват (OperationCanceledException ex), когда (например.CancellationToken == токен){cts. Dispose(); cts = null;}», который работает и исключает использование метода AllowColorChange. Кроме того, передача токена методу Delay теперь работает. Соответствуют ли эти изменения тому, что вы предложили?
4. Да, так намного лучше. Причина, по которой вам нужно устанавливать
colorPickerPtrPressed = true
каждые 100 мс, мне неясна, и я предполагаю, что это не оптимальное решение для того, что вы пытаетесь сделать, но, вероятно, оно достаточно хорошее. 🙂5. Каждый раз, когда я отправляю своему устройству команду color, я устанавливаю colorPickerPtrPressed = false в методе, который отправляет команду, чтобы предотвратить дальнейшие изменения при перемещении указателя из элемента управления выбора цвета. Но я также хотел иметь возможность удерживать нажатой кнопку мыши и посылать непрерывный поток, пока не получу нужный цвет. Мне просто не нужно или не хочется слишком перегружать мое устройство командами изменения цвета, когда достаточно периодического обновления. Еще раз спасибо за вашу помощь, это было очень поучительно.
Ответ №3:
После выполнения рекомендаций, предложенных Теодором Зоулиасом, окончательный код теперь выглядит так, как показано ниже. Можно видеть, что между отменой и удалением CancellationTokenSource не используется произвольная задержка, а скорее удаление перемещается в блок catch{}, который запускается OperationCanceledException, возникающим в результате выброса токена.ThrowIfCancellationRequested(); из цикла while(), который был перемещен внутри блока try{}, и его тестовому параметру присвоено значение true. Больше нет необходимости проверять значение токена.IsCancellationRequested запрашивается как параметр цикла while. Это кодирование гарантирует, что удаление не произойдет до тех пор, пока задача в блоке try{} не будет отменена.
private void ColorPicker_PntrPressedEvent(object sender, PointerRoutedEventArgs e)
{
if(cts == null) cts = new CancellationTokenSource();
token = cts.Token;
var picker = sender as ColorPicker.ColorPicker;
colorPickerPtrPressed = true; // True allows new values of color to be sent to device
(picker.DataContext as SpheroViewModel).Color = picker.SelectedColor.Color;
ColorChange(token); // Don't await this
}
private void ColorPicker_PntrReleasedEvent(object sender, PointerRoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}
public static async Task ColorChange(CancellationToken token)
{
try
{
await Task.Run(async () =>
{
while (true)
{
token.ThrowIfCancellationRequested();
colorPickerPtrPressed = true; // Maintain value as true while mouse button remains pressed
await Task.Delay(100, token); // allow color updates periodically
}
}, token);
}
catch (OperationCanceledException ex) when (ex.CancellationToken == token) // includes TaskCanceledException
{
if (cts != null) // Shouldn't arrive here if it is null but check just in case
{
try
{
cts.Dispose();
cts = null;
}
catch (ObjectDisposedException e)
{
// Already disposed, do nothing
bool brkPnt = true;
}
}
}
}
}
}
Комментарии:
1. Хм, обертывание цикла в a
Task.Run
усложняет ситуацию, потому что теперь не все будет происходить в одном потоке (потоке пользовательского интерфейса). Теперь возможно, что функцияcts.Cancel();
будет вызвана в потоке пользовательского интерфейса сразу послеcts.Dispose
того, как она была вызвана в фоновом потоке, что приведет кObjectDisposedException
. Я бы предложил удалитьTask.Run
, поскольку, похоже, он ничего не предлагает. Не о чем беспокоиться, вызывая продолжение в потоке пользовательского интерфейса каждые 100 мс, которое обновляет толькоbool
переменную.2. Кстати, рассматривали ли вы возможность замены этого механизма на простой
System.Windows.Forms.Timer
? Вы можете переключать егоEnabled
свойство (или вызывать егоStart
/Stop
методы) в соответствующие моменты.3. ОК. Кажется, что все работает так же хорошо без задачи. Запустите оболочку, как это было с ней.
4. TZ, просто к вашему сведению: после дальнейшего изучения я обнаружил, что с задачей. Присутствующая оболочка запуска, создание, отмена и удаление CancellationTokenSource происходят в потоке пользовательского интерфейса. Только цикл while() выполняется в потоке threadpool. Мне нравится эта конфигурация, потому что «ожидание задачи. Delay()» в цикле while() является необязательным, и если его нет, цикл while() заблокирует все дальнейшее выполнение, если не присутствует оболочка «await Task.Run». Я считаю, что окончательная версия кода, который я опубликовал, хороша. Моим намерением было реализовать асинхронный метод, и я верю, что с вашей помощью мне это удалось.