由于跳过空检查,BeginInvoke导致错误

时间:2016-02-08 17:46:55

标签: c# wpf multithreading begininvoke

我在多线程应用程序中使用Dispatcher.BeginInvoke时遇到了一个不可理解的错误。

我的程序包含List个对象,通过它我可以使用多个线程进行循环并执行一些计算。 我已将我的代码结构简化(并略微修改)为非常简单的基本要素,因此希望更容易理解:

public class Foo
{
    public void DoCalc()
    {
        //do calculations
    }
}

public class Foo2
{
    public Foo foo = new Foo();
    public Ellipse ellipse;
}

public partial class MainWindow : Window
{
    List<Foo2> myList = new List<Foo2>();
    List<Tuple<int, int>> indicesList = new List<Tuple<int, int>>();

    public MainWindow()
    {
        InitializeComponent();

        for (int i = 0; i < 10; ++i)
            myList.Add(new Foo2 { ellipse = new Ellipse() });
        for (int i = 10; i < 20; ++i)
            myList.Add(new Foo2());
        indicesList.Add(new Tuple<int, int>(0, 9));
        indicesList.Add(new Tuple<int, int>(10, 19));
    }

    private void OnStart(object sender, RoutedEventArgs e)
    {
        foreach (var t in indicesList)
            ThreadPool.QueueUserWorkItem(new WaitCallback(Loop), t);
    }

    private void Loop(object o)
    {
        Tuple<int, int> indices = o as Tuple<int, int>;

        for(int i = indices.Item1; i <= indices.Item2; ++i)
        {
            myList[i].foo.DoCalc();
            if (myList[i].ellipse == null)
                continue;

            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => myList[i].ellipse.Fill = Brushes.Black));
        }
    }
}

myList中的前半部分项目ellipse指向实际对象,而后半部分指向null。在Loop内,如果ellipsenull,我会在每次迭代时检查,并在必要时跳过该迭代。奇怪的是,在所有省略号都指向null的第二个线程中,程序仍然最终在第一个项目(索引10)上调用BeginInvoke操作,导致程序因空引用异常而崩溃。

如果我在行myList[i].foo.DoCalc();上放置一个断点并逐步慢慢浏览程序,则不会发生错误。此外,当我将BeginInvoke更改为Invoke时,不会发生错误。

据我所知,BeginInvoke异步工作是通过向Dispatcher发送请求以在某个时刻执行,而Invoke会阻塞调用线程,直到Dispatcher执行了请求。但是,由于我既没有访问两个循环中的相同元素,也没有更改关于列表本身的任何内容,所以我不明白BeginInvoke的多线程或异步性质如何以任何方式干扰我在这里做的事情。

不幸的是,我在SO上添加或删除项目时只发现与List连接的BeginInvoke错误,但是,当所有线程似乎始终访问不同的项目时,没有任何错误发生。如果我的代码有一些我根本不理解的基本问题(这可能导致我的程序实际上无法运行),那么请为我清除这个或给我一个回答的链接,我找不到。我一直坚持这个问题一整天了!

2 个答案:

答案 0 :(得分:2)

这可能与闭包有关......

试试这个:

  var current = myList[i].foo.DoCalc();
            if (current.ellipse == null)
                continue;    
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
             new Action(() => current.ellipse.Fill = Brushes.Black));

答案 1 :(得分:2)

您正在进行变量捕获。在调用调用中使用的i可能是indices.Item2 + 1,而不是它在执行BeginInvoke时具有的i值。您必须将i复制到每个循环迭代创建的本地变量。

private void Loop(object o)
{
    Tuple<int, int> indices = o as Tuple<int, int>;

    for(int i = indices.Item1; i <= indices.Item2; ++i)
    {
        myList[i].foo.DoCalc();
        if (myList[i].ellipse == null)
            continue;

        int iLocal = i;
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => myList[iLocal].ellipse.Fill = Brushes.Black));
    }
}
foreach之前的{p> C# 5有同样的问题,see here for more info