BeginInvoke 因跳过空检查而导致错误
BeginInvoke causes error due to skipped null check
我遇到了在多线程应用程序中使用 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
中,我会在每次迭代时检查 ellipse
是否为 null
,并在必要时跳过该迭代。奇怪的是,在所有省略号都指向 null 的第二个线程中,程序最终仍然在第一个项目(索引 10)上调用 BeginInvoke 操作,导致程序崩溃,原因是空引用异常。
如果我在myList[i].foo.DoCalc();
这一行打个断点,一步一步慢慢地执行程序,就不会出现错误。此外,当我将 BeginInvoke
更改为 Invoke
时,也不会发生错误。
据我了解,BeginInvoke 通过向 Dispatcher 发送请求以在某个时间点执行来异步工作,而 Invoke 会阻塞调用线程,直到 Dispatcher 执行了请求。但是,由于我既没有访问两个循环中的相同元素,也没有更改列表本身的任何内容,所以我不明白多线程或 BeginInvoke 的异步性质如何以任何方式干扰我在这里所做的事情。
不幸的是,在 SO 上添加或删除项目时,我只发现与 List
s 相关的 BeginInvoke 错误,但是,当所有线程似乎始终访问不同的项目时,没有发生任何错误。如果我的代码有一些我根本不理解的基本问题(这可能导致我的程序实际上无法运行),那么请为我解决这个问题或给我发一个 link 来回答,我找不到.我已经被这个问题困扰了一整天了!
这可能与闭包有关...
试试这个:
var current = myList[i].foo.DoCalc();
if (current.ellipse == null)
continue;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() => current.ellipse.Fill = Brushes.Black));
您正在 运行 进行变量捕获。在 invoke 调用中使用的 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
在 C# 5
之前有同样的问题,see here for more info。
我遇到了在多线程应用程序中使用 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
中,我会在每次迭代时检查 ellipse
是否为 null
,并在必要时跳过该迭代。奇怪的是,在所有省略号都指向 null 的第二个线程中,程序最终仍然在第一个项目(索引 10)上调用 BeginInvoke 操作,导致程序崩溃,原因是空引用异常。
如果我在myList[i].foo.DoCalc();
这一行打个断点,一步一步慢慢地执行程序,就不会出现错误。此外,当我将 BeginInvoke
更改为 Invoke
时,也不会发生错误。
据我了解,BeginInvoke 通过向 Dispatcher 发送请求以在某个时间点执行来异步工作,而 Invoke 会阻塞调用线程,直到 Dispatcher 执行了请求。但是,由于我既没有访问两个循环中的相同元素,也没有更改列表本身的任何内容,所以我不明白多线程或 BeginInvoke 的异步性质如何以任何方式干扰我在这里所做的事情。
不幸的是,在 SO 上添加或删除项目时,我只发现与 List
s 相关的 BeginInvoke 错误,但是,当所有线程似乎始终访问不同的项目时,没有发生任何错误。如果我的代码有一些我根本不理解的基本问题(这可能导致我的程序实际上无法运行),那么请为我解决这个问题或给我发一个 link 来回答,我找不到.我已经被这个问题困扰了一整天了!
这可能与闭包有关...
试试这个:
var current = myList[i].foo.DoCalc();
if (current.ellipse == null)
continue;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() => current.ellipse.Fill = Brushes.Black));
您正在 运行 进行变量捕获。在 invoke 调用中使用的 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
在 C# 5
之前有同样的问题,see here for more info。