【问题标题】:WPF/C# Don't block the UIWPF/C# 不要阻塞 UI
【发布时间】:2012-12-18 14:06:00
【问题描述】:

我有一个现有的 WPF 应用程序,它有几个部分。每个部分都是一个用户控件,它实现了一个接口。

接口指定两个方法:void LoadData([...])和bool UnloadData()。

这些方法是由 UI 线程调用的,所以如果很耗时,我们需要在 backgroundworker 中完成我们的工作。

LoadData 没有问题,因为我们可以异步更新 UI。问题在于 UnloadData()。

如果我们真的可以离开当前视图,这应该会返回。

这是根据数据的当前状态计算的(已保存/已修改/无效):

  • 保存返回真,
  • 无效的询问是否要留下来节省一些 更正数据或不保存就离开
  • 修改后告诉你可以 要么取消您的更改(返回 true),要么继续 编辑(返回 false),或者保存当前数据(返回 true)

问题在于“修改->保存”。这是一种耗时的方法,因此为了尊重应用程序的理念,我们应该在后台线程中运行它(带有繁忙指示器)。

但如果我们只是启动线程并进入下一部分,它会返回“true”到方法调用,我们将直接启动下一个视图。

就我而言,在保存本地数据之前加载下一个视图可能是个问题。

所以:

有没有办法在返回“true”之前等待后台线程完成,而不阻塞 UI?

public bool UnloadData(){
   if(...){
      LaunchMyTimeConsumingMethodWithBackgroundWorker();
      return true;//Only when my time consuming method ends
   }
   //[...]
}

重要编辑 也许我不够清楚:我知道如何使用 BackgroundWorker 或 TPL。我的问题是父类(调用 UnloadData() 的类)是一个我无法编辑的类(由于多种原因:它位于另一个不会重新加载的 DLL 中,它已经与 70 多个用户控件一起使用,全部分开projects(dll),通过反射加载。

这不是我的选择,我觉得不好,但我现在必须处理它。我主要是在寻找让我的方法等待我的方法返回的方法。我不确定这是否可能。但我正在寻找一种解决方法,它可以节省我数周的工作时间。

【问题讨论】:

    标签: c# .net wpf backgroundworker ui-thread


    【解决方案1】:

    好吧,现在我很兴奋,因为我想我可能自己发现了一些东西......

    所以,您要做的是:创建一个 DispatcherFrame,将该帧推送到 Dispatcher,然后在 RunWorkerCompleted 中将 Frame 的 Continue 设置为 false。

    这是目前为止的代码:

    public void Function()
    {
        BackgroundWorker worker = new BackgroundWorker();
        worker.DoWork += TimeConsumingFunction;
        var frame = new DispatcherFrame();
        worker.RunWorkerCompleted += (sender, args) =>
                                         {
                                             frame.Continue = false;
                                         };
        worker.RunWorkerAsync();
        Dispatcher.PushFrame(frame);
    }
    
    private void TimeConsumingFunction(object sender, DoWorkEventArgs doWorkEventArgs)
    {
        Console.WriteLine("Entering");
        for (int i = 0; i < 3; i++)
        {
            Thread.Sleep(1000);
        }
        Console.WriteLine("Exiting");
    }
    
    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        Function();
        Console.WriteLine("Returns");
    }
    

    【讨论】:

    • 我无法更改调用我的 userControls 方法的方式(70 多个用户控件已经实现了期望通过 UI 线程调用)。所以这必须在我的方法中完成。如果我使用后台线程,该方法将在我的耗时方法完成之前返回。
    • 所以如果我理解正确的话,你在 UI 线程上调用了一个方法,该方法在保存功能完成之前不应该返回,但你不想阻塞 UI 线程?跨度>
    • 完全正确(但不是我从 UI 线程进行调用,而是视图管理器,我对此无能为力)。
    • 试试我的编辑。但是,如果您不想阻塞 UI 线程,那么您就不走运了。在 UI 线程上不返回的方法将阻塞 UI 线程,直到它返回。
    • 这就是我所害怕的,但我希望有一些东西可以检查我们是否返回,让 UI 线程工作一点,...
    【解决方案2】:

    您应该实现一个 bool 类型的依赖属性“IsBusy”,在启动 BackgoundWorker 之前将其设置为 TRUE,然后在工作完成时设置为 FALSE。

    在 UI 上,您可以将在处理过程中想要禁用的任何功能绑定到该属性(例如用于加载下一个视图的按钮等);或者可能显示“取消”按钮。

    您不应“等待”操作完成,您可以在 BackgroundWorker 将设置的附加变量中检索结果:

        BackgroundWorker _bw;
        bool _returnValue = false;
    
        private void button_Click(object sender, RoutedEventArgs e)
        {  // if starting the processing by clicking a button
            _bw = new BackgroundWorker();
    
            IsBusy = true;
    
            _bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
    
            _bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted);
    
            _bw.RunWorkerAsync();
        }
    
        void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            IsBusy = false;
    
            // retrieve the result of the operation in the _returnValue variable
        }
    
    
        void _bw_DoWork(object sender, DoWorkEventArgs e)
        {
            _returnValue = UnloadData();
        }
    
        private bool UnloadData()
        {
            if (...)
            {
                LaunchTimeConsumingMethod();
                return true;
            }
            else
                return false;
    
            //etc ...
        }
    
        public bool IsBusy
        {
            get { return (bool)GetValue(IsBusyProperty); }
            set { SetValue(IsBusyProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for IsBusy.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsBusyProperty =
            DependencyProperty.Register( ... )
    

    【讨论】:

    • 我不能,这意味着改变我的 userControl "UnloadData" 方法的调用方式。
    • 嗯...是的,就是这样:)
    【解决方案3】:

    您或许可以尝试使用 .NET 4.5 的新“await”功能。

    await 关键字允许您等待 Task 对象的完成,而不会阻塞 UI。

    试试这个修改:

    public async bool UnloadData()
    {
        if(...)
        {
            await Task.Factory.StartNew(() => 
                {
                    LaunchMyTimeConsumingMethod();
                });
            return true;//Only when my time consuming method ends
        }
        //[...]
    }
    

    【讨论】:

    • "async bool UnloadData" 语法无效!异步方法的返回类型必须为 void、Task 或 Task
    【解决方案4】:

    将 UnloadData 视为异步操作,让 async/await 功能处理同步完成和需要异步完成的情况:

    public async Task<bool> UnloadData(){
        if(...){
            // The await keyword will segment your method execution and post the continuation in the UI thread
            // The Task.Factory.StartNew will run the time consuming method in the ThreadPool
            await Task.Factory.StartNew(()=>LaunchMyTimeConsumingMethodWithBackgroundWorker());
            // The return statement is the continuation and will run in the UI thread after the consuming method is executed
            return true;
        }
        // If it came down this path, the execution is synchronous and is completely run in the UI thread       
        return false;
    }
    
    
    private async void button_Click(object sender, RoutedEventArgs e)
    {
            // Put here your logic to prevent user interaction during the operation's execution.
            // Ex: this.mainPanel.IsEnabled = false;
            // Or: this.modalPanel.Visibility = Visible;
            // etc
    
            try
            {
                bool result = await this.UnloadData();
                // Do whatever with the result
            }
            finally
            {
                // Reenable the user interaction
                // Ex: this.mainPanel.IsEnabled = true;
            }
    }
    

    编辑

    如果您无法修改 UnloadData,则只需在 ThreadPool 上执行它,正如@BTownTKD 所述:

        private async void button_Click(object sender, RoutedEventArgs e)
    {
            // Put here your logic to prevent user interaction during the operation's execution.
            // Ex: this.mainPanel.IsEnabled = false;
            // Or: this.modalPanel.Visibility = Visible;
            // etc
    
            try
            {
    
                // The await keyword will segment your method execution and post the continuation in the UI thread
                // The Task.Factory.StartNew will run the time consuming method in the ThreadPool, whether it takes the long or the short path
                bool result = await The Task.Factory.StartNew(()=>this.UnloadData());
                // Do whatever with the result
            }
            finally
            {
                // Reenable the user interaction
                // Ex: this.mainPanel.IsEnabled = true;
            }
    }
    

    【讨论】:

      【解决方案5】:

      如果您的框架版本是 4.0,您可能应该使用 TPL:

      var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); // this will work only if you're running this code from UI thread, for example, by clicking a button
      Task.Factory.StartNew(() => UnloadData()).ContinueWith(t => /*update ui using t.Result here*/, uiScheduler);
      

      希望这会有所帮助。

      【讨论】:

      • 但是我不需要在这个方法中更新UI,我只需要在耗时方法退出时返回我当前的方法
      • 在continuation方法中处理UnloadData()的结果,这里可以询问是否成功-执行导航到其他视图,如果不成功-停留在当前。在上一个操作完成之前,延续委托不会启动。
      • 到另一个视图的导航在另一个类中管理,我无法编辑
      • 这个类如何确定是否需要导航到其他视图,这个类在哪里以及如何工作?
      • 它由返回实现的方法“UnloadData”的布尔值确定。它由“ViewManager”管理,从用户控件接收事件。
      【解决方案6】:

      你必须实现一个回调函数(RunWorkerCompleted),它在后台工作完成时调用。

      在此处查看示例: http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx

      【讨论】:

      • 我知道,但我的方法很久以前就会返回“true”并且已经改变了视图。
      • 一个选项不是在该方法中返回 true,而是在调用 RunWorkerCompleted 时调用一个方法来更新视图。编辑:对不起,我没有看到您使用的界面需要布尔结果。我不确定这是否有帮助
      • 没错,你现在看到了我的问题;)。我需要实现这个方法,我不能只是在我想要的时候做导航:(
      • @J4N 您可以通过轮询 BackgroundWorker 的 IsBusy 属性来做一些事情,如果没有看到整个代码,我不确定具体细节,但它可能会引导您找到解决方案。跨度>
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多