ObservableCollection 上的 BeginInvoke 不是立即执行

发布于 2024-08-30 21:51:34 字数 1417 浏览 11 评论 0原文

在我的代码中,我订阅了在不同线程上发生的事件。每次发生此事件时,我都会收到一个发布到可观察集合的字符串:

    Dispatcher currentDispatcher = Dispatcher.CurrentDispatcher;
    var SerialLog = new ObservableCollection<string>();

    private void hitStation_RawCommandSent(object sender, StringEventArgs e)
    {
        string command = e.Value.Replace("\r\n", "");
        Action dispatchAction = () => SerialLog.Add(command);
        currentDispatcher.BeginInvoke(dispatchAction, DispatcherPriority.Render);
    }

下面的代码位于我的视图模型中(可能位于后面的代码中,在这种情况下并不重要)。当我调用“hitstation.PrepareHit”时,上面的事件被调用几次,然后我等待并调用“hitStation.HitBall”,上面的事件又被调用几次。

    private void HitBall()
    {
        try
        {
            try
            {
                Mouse.OverrideCursor = Cursors.Wait;

                //prepare hit
                hitStation.PrepareHit(hitSpeed);

                Thread.Wait(1000);
                PlayWarning();

                //hit
                hitStation.HitBall(hitSpeed);
            }
            catch (TimeoutException ex)
            {
                MessageBox.Show("Timeout hitting ball: " + ex.Message);
            }
        }
        finally
        {
            Mouse.OverrideCursor = null;
        }
    }

我遇到的问题是,绑定到我的 SerialLog 的 ListBox 仅在 HitBall 方法完成时才会更新。我期待看到来自PrepareHit 的大量更新,暂停,然后来自HitBall 的更多更新。

我尝试了几个 DispatcherPriority 参数,但它们似乎没有任何效果。

In my code I subscribe to an event that happens on a different thread. Every time this event happens, I receive an string that is posted to the observable collection:

    Dispatcher currentDispatcher = Dispatcher.CurrentDispatcher;
    var SerialLog = new ObservableCollection<string>();

    private void hitStation_RawCommandSent(object sender, StringEventArgs e)
    {
        string command = e.Value.Replace("\r\n", "");
        Action dispatchAction = () => SerialLog.Add(command);
        currentDispatcher.BeginInvoke(dispatchAction, DispatcherPriority.Render);
    }

The code below is in my view model (could be in the code behind, it doesn't matter in this case). When I call "hitstation.PrepareHit", the event above gets called a couple times, then I wait and call "hitStation.HitBall", and the event above gets called a couple more times.

    private void HitBall()
    {
        try
        {
            try
            {
                Mouse.OverrideCursor = Cursors.Wait;

                //prepare hit
                hitStation.PrepareHit(hitSpeed);

                Thread.Wait(1000);
                PlayWarning();

                //hit
                hitStation.HitBall(hitSpeed);
            }
            catch (TimeoutException ex)
            {
                MessageBox.Show("Timeout hitting ball: " + ex.Message);
            }
        }
        finally
        {
            Mouse.OverrideCursor = null;
        }
    }

The problem I'm having is that the ListBox that is bound to my SerialLog gets updated only when the HitBall method finishes. I was expecting seeing a bunch of updates from the PrepareHit, a pause and then a bunch more updates from the HitBall.

I've tried a couple of DispatcherPriority arguments, but they don't seem to have any effect.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

我认为你正在阻碍自己。

UI 线程正在 Thread.Wait 等待,BeginInvoke 将操作发送到 dipatcher,但是 UI 线程正忙于等待。这就是为什么 UI 更新仅在 HitBall 完成后(即 UI 线程完成处理时)完成的原因。

为了解决这个问题,您应该在另一个线程中启动 HitBall 方法的代码,从而释放 UI:

 private void HitBall() 
 { 
     try {

         Mouse.OverrideCursor = Cursors.Wait;
         Dispatcher dispatcher = Dispatcher.CurrentDispatcher;

         Action hitOperations = () =>
         {
             hitStation.PrepareHit(hitSpeed);

             Thread.Wait(1000);

             //Only needed if PlayWarning needs access to UI Thread
             dispatcher.Invoke(() => PlayWarning());

             hitStation.HitBall(hitSpeed);

             dispatcher.Invoke(() => Mouse.OverrideCursor = null);
          };

          //Free the UI from work, start operations in a background thread
          hitOperations.BeginInvoke(null, null);

      }
      catch (TimeoutException ex)
      {
          MessageBox.Show("Timeout hitting ball: " + ex.Message);
      }
 }

此外,如果 Thread.Wait(1000) 的预期用途是等待事件刷新 UI,则不再需要此实现。

I think you are blocking yourself.

The UI Thread is waiting at Thread.Wait, BeginInvoke sends the action to the dipatcher, however the UI Thread is busy waiting. That is why UI updates only get done after HitBall finishes, that is, when the UI Thread finishes processing.

To get around this, you should start the HitBall method's code in another thread, freeing the UI:

 private void HitBall() 
 { 
     try {

         Mouse.OverrideCursor = Cursors.Wait;
         Dispatcher dispatcher = Dispatcher.CurrentDispatcher;

         Action hitOperations = () =>
         {
             hitStation.PrepareHit(hitSpeed);

             Thread.Wait(1000);

             //Only needed if PlayWarning needs access to UI Thread
             dispatcher.Invoke(() => PlayWarning());

             hitStation.HitBall(hitSpeed);

             dispatcher.Invoke(() => Mouse.OverrideCursor = null);
          };

          //Free the UI from work, start operations in a background thread
          hitOperations.BeginInvoke(null, null);

      }
      catch (TimeoutException ex)
      {
          MessageBox.Show("Timeout hitting ball: " + ex.Message);
      }
 }

Also if the intended use of Thread.Wait(1000) was to wait for events to refresh the UI, with this implementation it is no longer needed.

甜心 2024-09-06 21:51:34

难道是像需要在 ViewModel 上引发 PropertyChanged 事件一样简单吗?

Could it be something as simple as needing to raise the PropertyChanged event on your ViewModel?

浅听莫相离 2024-09-06 21:51:34

除非存在上下文切换,否则您可能看不到来自 PrepareHit 的更新,并且无法保证何时会发生上下文切换(除非您阻塞当前线程,这会增加发生上下文切换的可能性) )。

正如 iCe 提到的,如果您在 UI 线程上执行此操作,那么您可能会阻塞您的 UI。当您调用 Thread.Wait 时,您的 UI 是否会阻塞/冻结?如果没有,请继续阅读以下内容:

更新:
我想不出任何不会影响并发性的东西...在不影响并发性的情况下,您可以尝试提高 BeginInvoke 的优先级:http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx

currentDispatcher.BeginInvoke(dispatchAction, DispatcherPriority.Send);

顺便说一句,看起来 Render 优先级低于 Normal 优先级:

Render 枚举值为7。
同时处理的操作
优先级为渲染。

数据绑定
枚举值为8。操作为
以与数据相同的优先级处理
绑定。

Normal 枚举值
是 9。操作处理在
正常优先级。这是典型的
申请优先权。

发送
枚举值为10。 操作
在其他之前处理
异步操作。这是
最高优先级。

You will probably not see updates from PrepareHit unless there is a context switch and there is no guarantee when context switch will occur (unless you block your current thread and that would increase the probability that a context switch will occur).

As iCe mentioned if you're doing this on the UI thread, then you might be blocking your UI. Does your UI block/freeze when you call Thread.Wait? If it doesn't then proceed reading below:

Update:
I can't think of anything that would not compromise concurrency... without compromising concurrency you could try to increase the priority of BeginInvoke: http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx

currentDispatcher.BeginInvoke(dispatchAction, DispatcherPriority.Send);

By the way, it looks like Render priority is lower than Normal priority:

Render The enumeration value is 7.
Operations processed at the same
priority as rendering.

DataBind The
enumeration value is 8. Operations are
processed at the same priority as data
binding.

Normal The enumeration value
is 9. Operations are processed at
normal priority. This is the typical
application priority.

Send The
enumeration value is 10. Operations
are processed before other
asynchronous operations. This is the
highest priority.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文