我有一个使用多个线程的程序,当其中一个线程找到答案时(我认为上下文并不重要) - 它宣布它,然后我创建的第一个线程在用户控件中调用一个函数使用Invoke的类。 我检查了 - 如果我更改了这个函数中的任何属性,我就不会得到跨线程操作。但是这个函数启动一个计时器(System.Timers.Timer) - >所以调用“Elapsed”事件的功能。在其中我试图更改属性,这会导致跨线程操作。我究竟做错了什么?是不是可以让被调用的函数调用另一个函数,然后更改那里的属性?
顺便说一句,使用委托调用函数是错误的吗?我的意思是,将委托作为我需要的类的属性,然后使用delegAttributeName.Invoke(参数) - 而不是this.Invoke(new Delegate(),parameters);
代表部分代码:
这就是我调用函数的地方:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace Nim_Assignment_3
{
public delegate void drawDeleg(Color c, int amount, int rowNumber);
public partial class Nim : Form
{
private event drawDeleg myDrawDeleg;
private void CheckXor()
{
if (this.foundToPaint)
{
this.myDrawDeleg.Invoke(this.currentTurnColor, this.amountToPaint, this.rowToPaint);
this.drawWait.WaitOne();
this.foundToPaint = false;
if (this.currentTurnColor == Color.Blue)
this.currentTurnColor = Color.Red;
else
this.currentTurnColor = Color.Blue;
}
}
// the invoked function:
private void callFillPencils(Color c, int amount, int rowNumber)
{
this.rows[rowNumber].fillPencils(c, amount);
}
}
}
这是被调用函数调用的函数 - 以及它调用的函数(timer-elapsed事件函数): (fillPencils - Form类(Nim)中调用的函数正在调用的函数):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Timers;
namespace Nim_Assignment_3
{
public partial class PencilsUC : UserControl
{
private PictureBox[] pencils;
public static Image grayPencil = new Bitmap("GrayPen.bmp"), bluePencil = new Bitmap("BluePen.bmp"), redPencil = new Bitmap("RedPen.bmp");
private int amountOfPencils, amountOfPencilsLeft, currIndex, currAmount;
private System.Timers.Timer timer;
private Color currColor;
public event FinishedDrawing drawFinishedDeleg;
public PencilsUC()
{
// intializing things in the constructor...
this.timer = new System.Timers.Timer();
this.timer.Interval = 100;
this.timer.Elapsed += new ElapsedEventHandler(timer_Tick);
}
public void timer_Tick(object sender, EventArgs e)
{
// THE THING THAT MAKES THE CROSS THREAD-OPERATION: THE LINE INSIDE THE "if"
if (this.currColor == Color.Blue)
pencils[currIndex--].Image = bluePencil;
else
pencils[currIndex--].Image = redPencil;
this.currAmount--;
if (this.currAmount == 0)
{
this.timer.Stop();
if (this.drawFinishedDeleg != null)
this.drawFinishedDeleg.Invoke(this, new EventArgs());
}
}
public void fillPencils(Color c, int amount)
{
MessageBox.Show("Hello");
this.currColor = c;
this.currAmount = amount;
this.timer.Start();
}
}
}
(TIMER_TICK功能内的交叉线程操作)
我首先使用了windows窗体定时器,但由于某种原因它没有进入tick事件函数(timer.Start()被调用但是我在tick函数中放了一个消息框而且它没有进入那里所以我改变了 - 我看到一些答案说它更好了)
我会喜欢一些帮助,对不起这篇长篇文章感到抱歉,我只想尽可能地清楚...
提前多多感谢! :)
答案 0 :(得分:3)
使用Windows.Forms.Timer
代替System.Timers.Timer
。 (您需要更改一些属性/事件的名称,即Tick
而不是Elapsed
,但这很简单。)
表单命名空间中的Timer将Tick
事件编组到UI线程中,这与在线程池线程中执行事件的系统计时器不同。
如果你真的更喜欢使用系统的计时器,那么你可以设置SynchronizingObject
让它将它的事件编组到UI线程中:
timer.SynchronizingObject = this;
请注意,UserControl是可同步的对象。
答案 1 :(得分:2)
你需要.Invoke到主线程来改变任何控件。
Image image;
if (this.currColor == Color.Blue)
image = bluePencil;
else
image = redPencil;
this.Invoke(new MethodInvoker(() => pencils[currIndex--].Image = image));
=>是lambda的语法(在其他语言中称为匿名方法)。把它想象成一个单行函数。
() => pencils[currIndex--].Image = image
与:
相同void SetImage(Image image, ref int currIndex) {
pencils[currIndex--].Image = image;
}
MethodInvoker提供了一个简单的委托,用于调用带有void参数列表的方法
答案 2 :(得分:0)
您已经编写代码的最简单修复方法是将Timer的SynchronizingObject设置为Form,因此计时器将在UI线程上运行。