【问题标题】:Update SystemTray ProgressIndicator Value from Asynchronous Method从异步方法更新 SystemTray ProgressIndicator 值
【发布时间】:2013-09-12 23:45:43
【问题描述】:

我有一个长期运行的后台操作,用于将用户数据与远程 Web 服务同步,并希望向用户提供有关该操作状态的反馈。 SystemTray ProgressIndicator 在 XAML 中绑定到 ViewModel 上的属性,如下所示:

<shell:SystemTray.ProgressIndicator>
    <shell:ProgressIndicator IsVisible="{Binding IsSynchronizing, Mode=OneWay}"
                             Text="Synchronizing..."
                             Value="{Binding SyncProgress, Mode=OneWay}" />
</shell:SystemTray.ProgressIndicator>

这里的绑定在调用开始时就可以正常工作。我的异步 Web 服务操作是从 ViewModel 调用的,我为它提供了一个回调,以根据操作的当前状态设置其属性的值:

RemoteServiceManager.Current.UpdateUserDataAsync((progress) =>
{
    // Marshal to UI thread
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        IsSynchronizing = !progress.OperationFinished;
        SyncProgress = progress.PercentComplete;

        if (progress.OperationFinished)
        {
            OnDataChanged();
        }
    });
});

这种方法的问题是 UI 调度程序似乎永远不会调用操作(setter 永远不会被调用),如果我删除对调度程序的调用并直接执行委托内的代码,代码将执行而不生成异常,但进度指示器上的绑定属性不会使用新的进度值更新。如何从此长时间运行的异步操作中正确更新 ProgressInidcator Value 属性?

最后一个简短的问题:我在 ProgressIndicator 上设置的值应该是 0-1 还是 0-100?

【问题讨论】:

  • The problem with this approach is that the UI dispatcher never seems to invoke the action 如果调度程序真的从不调用您的操作,这意味着您正在 UI 线程上进行一些长时间运行的处理。你应该不惜一切代价避免这种事情
  • @KooKiz 我的 Web 服务客户端是使用异步调用生成的,我已经包装了它们中的每一个以符合异步/等待模式。 Web 服务交互未发生在 UI 线程上。用户界面非常敏感。当我点击 UpdateUserDataAsync 中的第一个 await 关键字时,我相对确定结果是在单独的线程上处理的。 ViewModel 也不在等待任何结果。
  • 很奇怪,如果 UI 是响应式的,那么我认为调度员没有理由不调用您的操作。你试过在IsSynchronizing = !progress.OperationFinished; 行设置断点吗?
  • @KooKiz 我做到了。似乎从来没有受到打击。我真的很困惑。我将尝试将其包装在任务工厂的操作中,看看会发生什么。
  • @KooKiz 看起来将调用包装在一个动作中并将其传递给任务工厂解决了这个问题。你的直觉是正确的。我可能需要更仔细地研究 async/await 的行为,因为我对发生的事情有一些错误的假设。如果你不介意写出来,我会接受你的回答。

标签: c# mvvm windows-phone-8 async-await


【解决方案1】:

据我所知,调度程序不会调用您的操作的唯一情况是 UI 线程已经很忙。

调用异步方法时,会捕获当前同步上下文。完成异步调用后,await 语句之后的代码将在捕获的同步上下文中执行。例如,同步上下文是 UI 线程。因此请务必记住,如果您在调用 await 之前处于 UI 线程中,那么在调用完成后您仍将处于该 UI 线程中。

此外,将任务视为线程是一个常见的错误。任务只是一个工作单元。它不一定在单独的线程中执行。实际上,如果您编写了一个异步方法但从未启动新线程,那么您的方法将在当前线程中执行。为什么会这样?仅仅是因为您并不总是需要一个线程来异步。例如,如果您只是订阅一个 I/O 事件,那么创建一个线程会浪费资源。

【讨论】:

    【解决方案2】:

    你可以尝试创建一个IProgress&lt;T&gt; 对象

    IProgress<ProgressInfo> progressIndicator = new Progress<ProgressInfo>(ReportProgress);
    

    并将其传递给您的 async 方法

    public async void YourMethodAsync(IProgress<ProgressInfo> progressIndicator)
    {
        ProgressInfo pi = new ProgressInfo(0);
        ...
        // Report progress.
        pi.PrecentageComplete = 30;
        progressIndicator.Report(pi);
        ...
    }
    

    在哪里

    public class ProgressInfo 
    {
        private int progressPercentage;
    
        public ProgressInfo() {}
    
        public ProgressInfo(int progressPercentage)
        {
            this.progressPercentage = progressPercentage;
        }
    
        public int ProgressPercentage
        {
            get { return progressPercentage; }
            set { progressPercentage = value; }
        }
    }
    

    ReportProgress 方法类似于

    public void ReportProgress(CostEngine.ProgressInfo progressInfo) 
    {
        SyncProgress = progressInfo.PercentComplete;
    }
    

    将在 UI 线程上自动调用。

    我希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-10
      • 2018-04-06
      • 2021-04-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多