WPF:如何同步异步加载的列表视图 [英] WPF: How to synchronize async loaded listviews

查看:117
本文介绍了WPF:如何同步异步加载的列表视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对加载资源异步与将所选元素保持在正确的加载资源之间的同步化有疑问.确切地说,我有一个包含用户的列表视图和一个包含他的个人资料的面板.如果选择该用户,则会从Web服务中加载该用户,然后在该配置文件面板中显示其数据.加载用户可能是一个非常昂贵的操作(时间),因此我尝试使异步加载避免阻止整个UI线程.我在ItemChange-Event中写了这样的东西->

I have a question about the syncronisation between loading resources async and keeping the selected element to the correct loaded resource. To be pricise I have a listview with users and one panel with his profile. If I choose that user, the user is loaded from an webservice and after that his data are shown in that profile-panel. Loading a user can be a very expensive operation (time) so I tried so make that loading async to prevent to block the whole UI-thread. I wrote in the ItemChange-Event something like this->

ItemChangeEvent(){
   Task.Factory.StartNew(()=>{
      .. load profile from Server
      this.Dispatcher.Invoke(.. some UI changes);
   });
}

现在有时发生的情况是,我在该列表视图中选择的用户不是配置文件中显示的用户.我的猜测是,正确"的用户配置文件任务完成后,任何任务都会延迟并推送其内容.那么如何实现加载是异步的却与当前选择的项目同步呢?

Now it sometimes happens, that the user I selected in that listview, is not the user which is shown on the profile. My guess is, that any of the task is delayed and pushed his content after the "correct" user-profile task is finished. So how can I achieve that the loading is async but syncronisation with the current-selected-item?

推荐答案

您可以添加 CancellationTokenSource 放在外部作用域中,然后将 CancellationToken 存储在事件处理程序内的本地变量中.理想情况下,此令牌应该由从远程服务器获取配置文件的方法传递和使用,以避免正在进行的任务获取不再需要的数据.

You could add a CancellationTokenSource in the outer scope, and store the CancellationToken in a local variable inside the event handler. Ideally this token should be passed and used by the method that fetches the profile from the remote server, to avoid having ongoing tasks fetching data that are no longer needed.

您还可以利用现代而简洁的

Also instead of using the awkward Dispatcher.Invoke for switching back to the UI thread, you could take advantage of the modern and neat async-await approach. The code after await continues automatically in the UI thread, without having to do anything special beyond adding the keyword async in the event handler:

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 控件.

It would be even more ideal if the expensive operation could become asynchronous. This way you would avoid blocking a ThreadPool thread every time the user clicked on the ListView control.

profile = await Task.Run(async () =>
{
    return await GetProfileAsync(id, token); // Expensive asynchronous operation
}, token);


更新:我尝试将与取消相关的逻辑封装在一个类中,以便可以用更少的代码行实现相同的功能.如果在同一窗口或多个窗口中多次重复执行此代码,可能会减少代码.该类名为 CancelableExecution ,并且具有单个方法 Run ,该方法以 Func< CancellationToken,T> 参数的形式接受可取消的操作..这是此类的用法示例:


Update: I made an attempt to encapsulate the cancellation-related logic inside a class, so that the same functionality can be achieved with fewer lines of code. It may be tempting to reduce this code in case it is repeated multiple times in the same window, or in multiple windows. The class is named CancelableExecution, and has a single method Run which accepts the cancelable operation in the form of a Func<CancellationToken, T> parameter. Here is a usage example of this class:

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可减少代码量,但也减少可读性,因此请谨慎使用此类!

The Run method returns a Task<bool>, that has the value true if the operation was completed successfully (not canceled). The result of a successful operation is available via an out Task<T> parameter. This API makes for less code, but also for less readable code, so use this class with caution!

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);
    }
}

这篇关于WPF:如何同步异步加载的列表视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆