Dispatcher.Invoke的竞争条件

本文关键字:条件 竞争 Invoke Dispatcher | 更新日期: 2023-09-27 18:11:52

我有一个WritableBitmap,我想得到它的尺寸。因为对象由另一个线程拥有,所以我们必须通过Dispatcher。我试过了:

int targetPixelWidth = 0;
int targetPixelHeight = 0;
writeableBitmap.Dispatcher.Invoke(new Action(() =>
{
    targetPixelWidth = writeableBitmap.PixelWidth;
    targetPixelHeight = writeableBitmap.PixelHeight;
}));
// Do something with targetPixelWidth and targetPixelHeight

然而,这有时会失败:值通常保持为0,即使实际值不同。

考虑到这可能是线程问题,我将代码更改如下:

var bitmapInfo = (Tuple<int, int>)writeableBitmap.Dispatcher.Invoke(new Func<Tuple<int, int>>(
   () => Tuple.Create(writeableBitmap.PixelWidth, writeableBitmap.PixelHeight)
));
Debug.Assert(bitmapInfo != null, "Obviously, this should pass.");
targetPixelWidth = bitmapInfo.Item1;
targetPixelHeight = bitmapInfo.Item2;
// Do something with targetPixelWidth and targetPixelHeight

但现在,bitmapInfo有时为空。这很奇怪,因为(根据文档)Invoke应该只在委托没有返回值时返回null,在这种情况下它显然是这样做的。我甚至把Debug.Assert的返回值改为Tuple.Create的返回值,而且它从来没有为空。

我在这里错过了什么?是什么导致了这种竞态,我能做些什么?

Dispatcher.Invoke的竞争条件

EDIT

很抱歉给出了错误的答案。

似乎你想从另一个线程中获得gui线程中的WriteableBitmap的依赖属性。

你可以试试:

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        var writeableBitmap = new WriteableBitmap(100, 100, 300, 300, PixelFormats.Bgra32, null);
        _image.Source = writeableBitmap;
        new Thread(() =>
            {
                Thread.Sleep(1000);
                var pixelHeigth = (Int32)writeableBitmap.Dispatcher.Invoke(
                                                     DispatcherPriority.Background,
                                                    (DispatcherOperationCallback)(arg => ((WriteableBitmap)arg).PixelHeight), writeableBitmap);
                Debug.Print("PixelHeight:" + pixelHeigth);
            }).Start();
    }

我刚试过,效果很好

这行得通,虽然我不知道为什么:

ManualResetEventSlim mre = new ManualResetEventSlim(false);
int targetPixelWidth = 0;
int targetPixelHeight = 0;
writeableBitmap.Dispatcher.Invoke(new Action(() =>
{
    try {
        targetPixelWidth = writeableBitmap.PixelWidth;
        targetPixelHeight = writeableBitmap.PixelHeight;
    }
    finally {
        mre.Set();
    }
}));
mre.Wait();
// Do something with targetPixelWidth and targetPixelHeight

有人(谁张贴了这个问题的答案,但后来删除了它),建议Invoke是同步的GUI线程,而不是在调用Invoke的线程。如果这是真的,这就解释了为什么它有效。然而,文档、书籍[1,2]和试图重现这个问题的小玩具程序都表明情况并非如此;Invoke在调用线程上应该是同步的。

我还没能想出一个真正的解释;如果有人有,我洗耳恭听:)。

EDIT用更连贯的东西替换我原来的,有点散乱的答案