Вы можете добавить CancellationTokenSource
во внешнюю область и сохранить CancellationToken
в локальной переменной внутри обработчика событий. В идеале этот токен должен передаваться и использоваться методом, который выбирает профиль с удаленного сервера, чтобы избежать выполнения текущими задачами выборки данных, которые больше не нужны.
Также вместо использования неудобного Dispatcher.Invoke
для переключения Возвращаясь к теме пользовательского интерфейса, вы можете воспользоваться современным и аккуратным подходом asyn c -await . Код после await
автоматически продолжается в потоке пользовательского интерфейса без необходимости делать что-то особенное, кроме добавления ключевого слова async
в обработчик событий:
private CancellationTokenSource _itemChangeTokenSource;
private async void ListView1_ItemChange(object sender, EventArgs e)
{
_itemChangeTokenSource?.Cancel();
_itemChangeTokenSource = new CancellationTokenSource();
CancellationToken token = _itemChangeTokenSource.Token;
var id = GetSelectedId(ListView1);
Profile profile;
try
{
profile = await Task.Run(() =>
{
return GetProfile(id, token); // Expensive operation
}, token);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
return; // Nothing to do, this event was canceled
}
UpdatePanel(profile);
}
Было бы еще лучше, если бы дорогая операция может стать асинхронным. Таким образом, вы бы не блокировали поток ThreadPool
каждый раз, когда пользователь нажимал на элемент управления ListView
.
profile = await Task.Run(async () =>
{
return await GetProfileAsync(id, token); // Expensive asynchronous operation
}, token);
Обновление: I сделал попытку инкапсулировать логику, связанную с отменой c, внутри класса, чтобы такая же функциональность могла быть достигнута с меньшим количеством строк кода. Может быть заманчиво уменьшить этот код в случае, если он повторяется несколько раз в одном и том же окне или несколько windows. Класс называется CancelableExecution
и имеет единственный метод Run
, который принимает отменяемую операцию в виде параметра Func<CancellationToken, T>
. Вот пример использования этого класса:
private CancelableExecution _updatePanelCancelableExecution = new CancelableExecution();
private async void ListView1_ItemChange(object sender, EventArgs e)
{
var id = GetSelectedId(ListView1);
if (await _updatePanelCancelableExecution.Run(cancellationToken =>
{
return GetProfile(id, cancellationToken); // Expensive operation
}, out var profile))
{
UpdatePanel(await profile);
}
}
Метод Run
возвращает значение Task<bool>
, которое имеет значение true
, если операция была успешно завершена (не отменена). Результат успешной операции доступен через параметр out Task<T>
. Этот API делает меньше кода, но также и менее читаемый код, поэтому используйте этот класс с осторожностью!
public class CancelableExecution
{
private CancellationTokenSource _activeTokenSource;
public Task<bool> RunAsync<T>(Func<CancellationToken, Task<T>> function,
out Task<T> result)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var resultTcs = new TaskCompletionSource<T>(
TaskCreationOptions.RunContinuationsAsynchronously);
result = resultTcs.Task;
return ((Func<Task<bool>>)(async () =>
{
try
{
var oldTokenSource = Interlocked.Exchange(ref _activeTokenSource,
tokenSource);
if (oldTokenSource != null)
{
await Task.Run(() =>
{
oldTokenSource.Cancel(); // Potentially expensive
}).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
}
var task = function(token);
var result = await task.ConfigureAwait(false);
token.ThrowIfCancellationRequested();
resultTcs.SetResult(result);
return true;
}
catch (OperationCanceledException ex) when (ex.CancellationToken == token)
{
resultTcs.SetCanceled();
return false;
}
catch (Exception ex)
{
resultTcs.SetException(ex);
throw;
}
finally
{
if (Interlocked.CompareExchange(
ref _activeTokenSource, null, tokenSource) == tokenSource)
{
tokenSource.Dispose();
}
}
}))();
}
public Task<bool> RunAsync<T>(Func<Task<T>> function, out Task<T> result)
{
return RunAsync(ct => function(), out result);
}
public Task<bool> Run<T>(Func<CancellationToken, T> function, out Task<T> result)
{
return RunAsync(ct => Task.Run(() => function(ct), ct), out result);
}
public Task<bool> Run<T>(Func<T> function, out Task<T> result)
{
return RunAsync(ct => Task.Run(() => function(), ct), out result);
}
}