C# WPF:如何同步异步加载的ListView
我有一个关于异步加载资源和将所选元素保持为正确加载资源之间的同步的问题。确切地说,我有一个用户列表视图和一个有他的个人资料的面板。如果我选择该用户,该用户将从Web服务加载,然后他的数据将显示在该配置文件面板中。加载用户可能是一个非常昂贵的操作(时间),因此我尝试使加载异步,以防止阻塞整个UI线程。我在ItemChange事件中写了这样的东西->C# WPF:如何同步异步加载的ListView,c#,wpf,user-interface,asynchronous,C#,Wpf,User Interface,Asynchronous,我有一个关于异步加载资源和将所选元素保持为正确加载资源之间的同步的问题。确切地说,我有一个用户列表视图和一个有他的个人资料的面板。如果我选择该用户,该用户将从Web服务加载,然后他的数据将显示在该配置文件面板中。加载用户可能是一个非常昂贵的操作(时间),因此我尝试使加载异步,以防止阻塞整个UI线程。我在ItemChange事件中写了这样的东西-> ItemChangeEvent(){ Task.Factory.StartNew(()=>{ .. load profile
ItemChangeEvent(){
Task.Factory.StartNew(()=>{
.. load profile from Server
this.Dispatcher.Invoke(.. some UI changes);
});
}
有时,我在列表视图中选择的用户不是概要文件中显示的用户。我的猜测是,在“正确的”用户配置文件任务完成后,任何任务都会延迟并推送其内容。那么,如何实现加载是异步的,但与当前选定的项目同步?我建议您在选择其他用户后使用
CancellationToken
取消以前的加载任务。这可以通过几个步骤实现:
CancellationTokenSource\u tokenSource
CancellationToken
存储在事件处理程序中的局部变量中。理想情况下,从远程服务器获取概要文件的方法应该传递并使用此令牌,以避免正在进行的任务获取不再需要的数据
您还可以利用现代而简洁的方法,而不是使用笨拙的Dispatcher.Invoke
切换回UI线程。wait
之后的代码将在UI线程中自动继续,无需执行任何特殊操作,只需在事件处理程序中添加关键字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);
}
如果昂贵的操作可以变成异步的,那就更理想了。这样可以避免用户每次单击ListView
控件时阻塞线程
profile = await Task.Run(async () =>
{
return await GetProfileAsync(id, token); // Expensive asynchronous operation
}, token);
更新:我尝试将与取消相关的逻辑封装在一个类中,这样就可以用更少的代码行实现相同的功能。如果在同一个窗口或多个窗口中重复多次,则可能会减少此代码。该类被命名为
CancelableExecution
,并且有一个方法Run
,该方法以Func
参数的形式接受可取消的操作。
下面是此类的使用示例:
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
,如果操作成功完成(未取消),则该任务的值为true
。成功操作的结果可通过out Task
参数获得。这个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);
}
}
public类CancelableExecution
{
私有CancellationTokenSource\u activeTokenSource;
公共任务RunAsync(Func函数,
输出任务结果)
{
var tokenSource=new CancellationTokenSource();
var token=tokenSource.token;
var resultTcs=新任务完成源(
TaskCreationOptions.RunContinuationsAsynchronously);
结果=resultTcs.Task;
返回((Func)(异步()=>
{
尝试
{
var oldTokenSource=Interlocked.Exchange(ref\u activeTokenSource,
代币来源);
if(oldTokenSource!=null)
{
等待任务。运行(()=>
{
oldTokenSource.Cancel();//可能很昂贵
}).配置等待(错误);
token.ThrowIfCancellationRequested();
}
var任务=功能(令牌);
var result=await task.ConfigureAwait(false);
token.ThrowIfCancellationRequested();
resultTcs.SetResult(result);
返回true;
}
捕获(OperationCanceledException ex)时(ex.CancellationToken==token)
{
resultTcs.setCancelled();
返回false;
}
捕获(例外情况除外)
{
resultTcs.SetException(ex);
投
}
最后
{
如果(联锁。比较交换)(
ref _activeTokenSource,null,tokenSource)=tokenSource)
{
tokenSource.Dispose();
}
}
}))();
}
公共任务RunAsync(Func函数,输出任务结果)
{
返回RunAsync(ct=>function(),out结果);
}
公共任务运行(Func函数,输出任务结果)
{
返回RunAsync(ct=>Task.Run(()=>function(ct),ct),输出结果);
}
公共任务运行(Func函数,输出任务结果)
{
返回RunAsync(ct=>Task.Run(()=>function(),ct),输出结果);
}
}
尝试使用与锁的同步
或其他构造。ReactiveUI有一个很好的功能来处理这个问题,请参见获取想法AI在代码中添加了“throwifcancellationrequest”方法,但它仍然不起作用:/@EnemyArea基本想法是正确的,但要正确完成它还有很多工作要做。ReactiveUI已经为您完成了这项工作,您只需使用it@EnemyArea我更新了我的答案,添加了相同想法的组件化实现。@EnemyArea此解决方案(通常是异步等待解决方案)的一个可能问题是,在关闭窗口时,您可能会得到一个ObjectDisposedException
,异步操作仍在后台运行时。我确信这对Windows窗体来说是个问题,但我不确定WPF是否正确。如果出现此问题,可以在窗口.Closed
事件上设置一个标志,然后在e上检查此标志
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);
}
}