3

我有一个现有的 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 多个 userControls 一起使用,全部分开项目(dll),通过反射加载。

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

4

6 回答 6

5

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

所以,你要做的是:创建一个 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");
}
于 2012-12-18T14:12:50.870 回答
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( ... )
于 2012-12-18T14:50:55.010 回答
2

您可以尝试使用 .NET 4.5 的新“等待”功能。

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

试试这个修改:

public async bool UnloadData()
{
    if(...)
    {
        await Task.Factory.StartNew(() => 
            {
                LaunchMyTimeConsumingMethod();
            });
        return true;//Only when my time consuming method ends
    }
    //[...]
}
于 2012-12-18T15:07:50.723 回答
1

将 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;
        }
}
于 2012-12-18T15:06:42.357 回答
0

如果您的框架版本是 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);

希望这可以帮助。

于 2012-12-18T14:13:15.257 回答
0

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

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

于 2012-12-18T14:14:20.310 回答