线程内调用的方法以意外的顺序执行 (C# .net)
Method called within thread executes in unexpected order (C# .net)
寻求一般帮助以理解此程序流程:
在 windows 表单应用程序中,为什么当我在新线程中调用方法时,该线程不等待方法完成就继续执行?
线程内调用的方法默认是异步执行的吗? (我希望程序在方法完成之前阻塞,而不必使用下面的 Thread.Sleep 行)。下面 "Thread.Sleep" 行的评论可能有助于进一步澄清我的问题。 - 谢谢!
private void button1_Click(object sender, EventArgs e)
{
//Call doStuff in new thread:
System.Threading.Thread myThread;
myThread = new System.Threading.Thread(new System.Threading.ThreadStart(doStuff));
myThread.Start();
}
private void doStuff()
{
//Instantiate external class used for threadSafe interaction with UI objects:
ThreadSafeCalls tsc = new ThreadSafeCalls();
int indx_firstColumn = 0;
//Loop 3 times, add a row, and add value of "lastRowAdded" to column1 cell.
for (int a = 0; a < 3; a += 1)
{
tsc.AddGridRow(dataGridView1); //Call method to add a row to gridview:
Thread.Sleep(1000); // Why does the execution of the line above go all crazy if I don't pause here? It's executing on the same thread, shouldn't it be synchronous?
tsc.AddGridCellData(dataGridView1,indx_firstColumn, tsc.lastRowAdded,tsc.lastRowAdded.ToString()); //Add value of "lastRowAdded" (for visual debugging)
}
"ThreadSafeCalls"class的内容:
public int lastRowAdded = -999;
public void AddGridRow(DataGridView gv)
{
if (gv.InvokeRequired)
{
gv.BeginInvoke((MethodInvoker)delegate ()
{
lastRowAdded = gv.Rows.Add();
});
}
else
{
lastRowAdded = gv.Rows.Add();
}
}
public void AddGridCellData(DataGridView gv, int column, int rowID, string value)
{
//Thread safe:
if (gv.InvokeRequired)
{
gv.BeginInvoke((MethodInvoker)delegate () { gv.Rows[rowID].Cells[column].Value = value + " "; });
}
else
{
gv.Rows[rowID].Cells[column].Value = value;
}
}
来自搜索结果:
Control.BeginInvoke Method (System.Windows.Forms)
在创建控件基础句柄的线程上异步执行委托。在线程上异步执行指定委托
来源:Control.BeginInvoke Method 在 msdn 上。
编辑: 你说的 "go all crazy" 的意思不是很清楚,但在盯着代码看了很长一段时间后,我意识到有一场比赛 window 在不使用睡眠时在循环中表现出来。我运行这个剥离版多次确认。为冗长的编辑道歉,但没有细节很难理解这个问题。
//Loop 3 times, add a row, and add value of "lastRowAdded" to column1 cell.
for (int a = 0; a < 3; a += 1)
{
tsc.AddGridRow(dataGridView1); //Call method to add a row to gridview:
Thread.Sleep(1000); // Why does the execution of the line above go all crazy if I don't pause here? It's executing on the same thread, shouldn't it be synchronous?
tsc.AddGridCellData(dataGridView1,indx_firstColumn, tsc.lastRowAdded,tsc.lastRowAdded.ToString()); //Add value of "lastRowAdded" (for visual debugging)
}
分解:
- 安排在 UI 线程上添加一行并更新
lastRowAdded
。
- 休眠 1 秒。遗漏它会导致种族显现。
- 传递
lastRowAdded
的值和等效字符串,因为 myThread
记得它,安排要在 UI 线程上更新的单元格。
- 重复 3 次。
您遇到这种情况的原因是 caching。本质上,当您忽略睡眠 myThread
时,会看到 lastRowAdded
的陈旧版本,然后将陈旧的副本传递给 AddGridCellData
。此陈旧值传播到 UI 线程,通常会导致 -999 或其他一些不正确的行索引。有时您应该会收到 IndexOutOfRangeException
,但并非总是如此。休眠正好给 UI 线程足够的时间将其缓存的值写回主内存,然后 myThread
读取更新的值。根据 OS 在哪些内核上调度线程,它似乎在某些运行中正常运行而在其他运行中不正确。
要解决此问题,您需要移除睡眠并同步访问 ThreadSafeCalls
中的所有可变数据。最简单的方法是使用锁。
即
循环实现:
for (int a = 0; a < 3; a += 1)
{
tsc.AddGridRow(dataGridView1);
tsc.AddGridCellData(dataGridView1, indx_firstColumn);
}
TSC 实施:
class ThreadSafeCalls
{
private object syncObject = new object();
private int lastRowAdded = -999;
public int LastRowAdded
{
get {
lock (syncObject) {
return lastRowAdded;
}
}
set {
lock (syncObject) {
lastRowAdded = value;
}
}
}
public void AddGridRow(DataGridView gv)
{
if (gv.InvokeRequired) {
gv.BeginInvoke((MethodInvoker)delegate () {
LastRowAdded = gv.Rows.Add();
});
}
else {
LastRowAdded = gv.Rows.Add();
}
}
public void AddGridCellData(DataGridView gv, int column)
{
if (gv.InvokeRequired) {
gv.BeginInvoke((MethodInvoker)delegate () {
var lastRow = LastRowAdded;
gv.Rows[lastRow].Cells[column].Value = lastRow + " ";
});
} else {
var lastRow = LastRowAdded;
gv.Rows[lastRow].Cells[column].Value = lastRow + " ";
}
}
}
原答案:
Are methods called within a thread executed asynchronously by default?
Yes 指的是针对创建线程执行 ThreadStart
目标方法;这就是线程的意义所在。您可以使用额外的线程来执行后台加载或额外处理等操作,而不会阻塞主线程。
(I would expect the program to block until the method is complete
without having to use the Thread.Sleep line below).
BeginInvoke
向 UI 线程调度程序添加一个方法,以便稍后在 UI 线程 上 调用之前正在做(即处理 OS 事件,执行在您添加的方法之前安排的方法)。这是延迟执行,returns 一旦被调度,这就是它不阻塞的原因。执行被推迟到 UI 线程的原因是大多数 UI 框架不是线程安全的。修改 UI 的多个线程会导致数据竞争,从而产生各种问题。
附带说明一下,您真的应该避免创建线程来响应用户输入。线程是昂贵的资源,应该尽快创建(最好是在初始化时)。在您的示例中,每次单击按钮时都会创建一个线程,这非常慢。
寻求一般帮助以理解此程序流程: 在 windows 表单应用程序中,为什么当我在新线程中调用方法时,该线程不等待方法完成就继续执行?
线程内调用的方法默认是异步执行的吗? (我希望程序在方法完成之前阻塞,而不必使用下面的 Thread.Sleep 行)。下面 "Thread.Sleep" 行的评论可能有助于进一步澄清我的问题。 - 谢谢!
private void button1_Click(object sender, EventArgs e)
{
//Call doStuff in new thread:
System.Threading.Thread myThread;
myThread = new System.Threading.Thread(new System.Threading.ThreadStart(doStuff));
myThread.Start();
}
private void doStuff()
{
//Instantiate external class used for threadSafe interaction with UI objects:
ThreadSafeCalls tsc = new ThreadSafeCalls();
int indx_firstColumn = 0;
//Loop 3 times, add a row, and add value of "lastRowAdded" to column1 cell.
for (int a = 0; a < 3; a += 1)
{
tsc.AddGridRow(dataGridView1); //Call method to add a row to gridview:
Thread.Sleep(1000); // Why does the execution of the line above go all crazy if I don't pause here? It's executing on the same thread, shouldn't it be synchronous?
tsc.AddGridCellData(dataGridView1,indx_firstColumn, tsc.lastRowAdded,tsc.lastRowAdded.ToString()); //Add value of "lastRowAdded" (for visual debugging)
}
"ThreadSafeCalls"class的内容:
public int lastRowAdded = -999;
public void AddGridRow(DataGridView gv)
{
if (gv.InvokeRequired)
{
gv.BeginInvoke((MethodInvoker)delegate ()
{
lastRowAdded = gv.Rows.Add();
});
}
else
{
lastRowAdded = gv.Rows.Add();
}
}
public void AddGridCellData(DataGridView gv, int column, int rowID, string value)
{
//Thread safe:
if (gv.InvokeRequired)
{
gv.BeginInvoke((MethodInvoker)delegate () { gv.Rows[rowID].Cells[column].Value = value + " "; });
}
else
{
gv.Rows[rowID].Cells[column].Value = value;
}
}
来自搜索结果:
Control.BeginInvoke Method (System.Windows.Forms)
在创建控件基础句柄的线程上异步执行委托。在线程上异步执行指定委托
来源:Control.BeginInvoke Method 在 msdn 上。
编辑: 你说的 "go all crazy" 的意思不是很清楚,但在盯着代码看了很长一段时间后,我意识到有一场比赛 window 在不使用睡眠时在循环中表现出来。我运行这个剥离版多次确认。为冗长的编辑道歉,但没有细节很难理解这个问题。
//Loop 3 times, add a row, and add value of "lastRowAdded" to column1 cell.
for (int a = 0; a < 3; a += 1)
{
tsc.AddGridRow(dataGridView1); //Call method to add a row to gridview:
Thread.Sleep(1000); // Why does the execution of the line above go all crazy if I don't pause here? It's executing on the same thread, shouldn't it be synchronous?
tsc.AddGridCellData(dataGridView1,indx_firstColumn, tsc.lastRowAdded,tsc.lastRowAdded.ToString()); //Add value of "lastRowAdded" (for visual debugging)
}
分解:
- 安排在 UI 线程上添加一行并更新
lastRowAdded
。 - 休眠 1 秒。遗漏它会导致种族显现。
- 传递
lastRowAdded
的值和等效字符串,因为myThread
记得它,安排要在 UI 线程上更新的单元格。 - 重复 3 次。
您遇到这种情况的原因是 caching。本质上,当您忽略睡眠 myThread
时,会看到 lastRowAdded
的陈旧版本,然后将陈旧的副本传递给 AddGridCellData
。此陈旧值传播到 UI 线程,通常会导致 -999 或其他一些不正确的行索引。有时您应该会收到 IndexOutOfRangeException
,但并非总是如此。休眠正好给 UI 线程足够的时间将其缓存的值写回主内存,然后 myThread
读取更新的值。根据 OS 在哪些内核上调度线程,它似乎在某些运行中正常运行而在其他运行中不正确。
要解决此问题,您需要移除睡眠并同步访问 ThreadSafeCalls
中的所有可变数据。最简单的方法是使用锁。
即
循环实现:
for (int a = 0; a < 3; a += 1)
{
tsc.AddGridRow(dataGridView1);
tsc.AddGridCellData(dataGridView1, indx_firstColumn);
}
TSC 实施:
class ThreadSafeCalls
{
private object syncObject = new object();
private int lastRowAdded = -999;
public int LastRowAdded
{
get {
lock (syncObject) {
return lastRowAdded;
}
}
set {
lock (syncObject) {
lastRowAdded = value;
}
}
}
public void AddGridRow(DataGridView gv)
{
if (gv.InvokeRequired) {
gv.BeginInvoke((MethodInvoker)delegate () {
LastRowAdded = gv.Rows.Add();
});
}
else {
LastRowAdded = gv.Rows.Add();
}
}
public void AddGridCellData(DataGridView gv, int column)
{
if (gv.InvokeRequired) {
gv.BeginInvoke((MethodInvoker)delegate () {
var lastRow = LastRowAdded;
gv.Rows[lastRow].Cells[column].Value = lastRow + " ";
});
} else {
var lastRow = LastRowAdded;
gv.Rows[lastRow].Cells[column].Value = lastRow + " ";
}
}
}
原答案:
Are methods called within a thread executed asynchronously by default?
Yes 指的是针对创建线程执行 ThreadStart
目标方法;这就是线程的意义所在。您可以使用额外的线程来执行后台加载或额外处理等操作,而不会阻塞主线程。
(I would expect the program to block until the method is complete without having to use the Thread.Sleep line below).
BeginInvoke
向 UI 线程调度程序添加一个方法,以便稍后在 UI 线程 上 调用之前正在做(即处理 OS 事件,执行在您添加的方法之前安排的方法)。这是延迟执行,returns 一旦被调度,这就是它不阻塞的原因。执行被推迟到 UI 线程的原因是大多数 UI 框架不是线程安全的。修改 UI 的多个线程会导致数据竞争,从而产生各种问题。
附带说明一下,您真的应该避免创建线程来响应用户输入。线程是昂贵的资源,应该尽快创建(最好是在初始化时)。在您的示例中,每次单击按钮时都会创建一个线程,这非常慢。