使用带有Dispatcher的BackgroundWorker似乎没有任何作用

3
我正在尝试更新一个与UI数据绑定的ObservableCollection。我知道要做到这一点,我需要使用DispatcherBeginInvoke(),为了使UI不会在此过程中冻结,使用BackgroundWorker是一个好的方法。无论如何,我已经编译并运行了所有内容,但什么也没有发生。我需要每2分钟左右更新UI,因此我还使用了DispatcherTimer
这有效,因为DispatcherTimer是Dispatcher的一部分,但会冻结UI:
DispatcherTimer dispTimer = new DispatcherTimer();
dispTimer.Tick += dispTimer_Tick;
dispTimer.Interval = new TimeSpan(0, 0, 45);
dispTimer.Start();

private void dispTimer_Tick(object sender, EventArgs e)
{
    PartialEmployees.Clear();          
}

因此,使用BackgroundWorker,我将其组合成以下内容:
DispatcherTimer dispTimer = new DispatcherTimer();
dispTimer.Tick += dispTimer_Tick;
dispTimer.Interval = new TimeSpan(0, 0, 45);
dispTimer.Start();

private void dispTimer_Tick(object sender, EventArgs e)
{
    BackgroundWorker _worker = new BackgroundWorker();
    _worker.DoWork += DoWork;            
    _worker.RunWorkerAsync();

}

private void DoWork(object sender, DoWorkEventArgs e)
{            
    Dispatcher.CurrentDispatcher.BeginInvoke( new Action(()=> 
        {
            PartialEmployees.Clear();
        }));
} 

但是UI没有任何反应。我错过了什么/做错了什么?
3个回答

3
您有两个问题:
1. 当您从后台线程使用Dispatcher.CurrentDispatcher时,它获取的是后台线程的Dispatcher,而不是UI线程的Dispatcher。 2. 根据您的描述,我了解到您的PartialEmployees.Clear()方法执行需要花费相当长的时间,并且您想在执行期间避免锁定UI线程。然而,让BackgroundWorker在UI线程上调用PartialEmployees.Clear()将产生与使用DispatcherTimer相同的效果,因此您需要一个不同于您正在寻找的解决方案。
如果您只想解决Dispatcher.CurrentDispatcher问题,只需像这样将当前Dispatcher存储在本地变量中即可:
private void dispTimer_Tick(object sender, EventArgs e) 
{
  var uiDispatcher = Dispatcher.CurrentDispatcher;

  BackgroundWorker _worker = new BackgroundWorker(); 
  _worker.DoWork += (sender, e) =>
    uiDispatcher.BeginInvoke(new Action(() =>
    {
      PartialEmployees.Clear();
    }));
  _worker.RunWorkerAsync(); 
} 

这将导致您的UI更改有效,但在更改期间仍会锁定UI,就像您没有使用BackgroundWorker一样。原因如下:
  1. DispatcherTimer触发,在UI线程上执行。它所做的所有事情(dispTimer_Tick)都是启动一个BackgroundWorker,然后退出。
  2. BackgroundWorker在其自己的线程上执行。它所做的所有事情都是安排一个Dispatcher回调,然后退出。
  3. Dispatcher回调再次在UI线程上执行。它调用PartialEmployees.Clear()需要一段时间,而在执行期间锁定了您的UI线程。
因此,您的行为与DispatcherTimer回调直接调用PartialEmployees.Clear()的情况相同:在每种情况下,耗时的操作都在UI线程上执行。
锁定的原因是,任何时候在UI线程上执行大量工作,您都会在运行时获得短暂的锁定。解决方法是将工作分成较小的部分,并逐一完成它们,无论是从DispatcherTimer还是BackgroundWorker进行。在您的情况下,请检查PartialEmployees.Clear()的代码,看看是否可以逐步完成。

好的。也许我有点困惑。BackgroundWorker 的目的不是在后台执行工作,以便 UI 不会冻结;然后使用 Dispatcher 将更改传递到 UI 线程吗?我正在使用 EntityFramework,.Clear() 已经为列表内置了。我不想在 UI 线程中执行工作,我只想“刷新”绑定到 UI 的列表。我想也许这是不可能的,我将不得不添加一个进度条,以便用户知道正在发生某些事情,应用程序没有冻结。 - LobalOrning
在这种情况下,您可能需要将UI绑定到不由EntityFramework维护的集合,并且当EntityFramework集合触发CollectionChanged时,使用Dispatcher.Invoke在绑定的集合中进行相应的更改,以便UI绑定的集合在UI线程上更新。您对BackgroundWorker的目的是正确的,实际上它将适用于通过EntityFramework 加载数据的目的。只要您以小块更新UI,UI就不会冻结。 - Ray Burns

1
问题在于你正在后台线程中使用方法Dispatcher.CurrentDispatcher。你需要的是UI线程的Dispatcher实例。
_worker.DoWork += delegate { DoWork(Dispatcher.CurrentDispatcher); };

...
private void DoWork(Dispatcher dispatcher) {
  dispatcher.BeginInvoke(new Action(() => {
    PartialEmployees.Clear();
  });
}

2
你也可以直接使用MainWindow.Dispatcher或App.Current.Dispatcher(假设你没有进行任何复杂的操作,如在多个线程上运行runloops)。 - Ana Betts
这个也不行,似乎它正在做工作,但界面没有改变。我想我没有使用UI Dispatcher,所以它在不同的线程上执行工作,然后UI就永远不知道了。我不能使用MainWindow.Dispatcher,因为它需要“一个对象参考”...不确定这指的是什么。 - LobalOrning

0

我认为你不需要背景工作,因为在Dispatcher上的BeginInvoke会在线程池线程上运行。

像这样的东西应该可以工作,并且更加简洁。

DispatcherTimer dispTimer = new DispatcherTimer 
    {Interval = TimeSpan.FromSeconds(45)};
dispTimer.Tick += (o,e) => Dispatcher.CurrentDispatcher
    .BeginInvoke((Action)PartialEmployees.Clear);
dispTimer.Start();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接