0

我有一个使用许多线程的程序,当其中一个线程找到答案时(我认为上下文并不重要) - 它宣布它,然后我创建的第一个线程使用 Invoke 调用用户控件类中的函数. 我检查了 - 如果我更改此函数中的任何属性,我不会得到跨线程操作。但是这个函数启动了一个计时器(System.Timers.Timer)-> 所以调用了“Elapsed”事件的函数。在里面我试图改变一个属性,这会导致跨线程操作。我究竟做错了什么?是不是可以让被调用的函数调用另一个函数然后改变那里的属性?

顺便说一句,使用委托调用函数是错误的吗?我的意思是,将委托作为我需要它的类的属性,然后使用 delegAttributeName.Invoke(parameters) - 而不是 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);
  }
 }
}

这是被调用函数正在调用的函数 - 也是它调用的函数(计时器经过的事件函数):(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 窗体计时器,但由于某种原因,它没有进入滴答事件函数(调用了 timer.Start(),但我在滴答函数中放置了一个消息框,但它没有进入,所以我改变了它 - 我看到一些答案说它更好)

我希望得到一些帮助,我很抱歉发了这么长的帖子,我只是想尽可能清楚......

提前非常感谢!:)

4

3 回答 3

3

使用 aWindows.Forms.Timer而不是 a System.Timers.Timer。(您需要更改一些属性/事件的名称,即Tick代替Elapsed,但这很简单。)

Form 命名空间中的 Timer 将Tick事件编组到 UI 线程中,这与在线程池线程中执行事件的系统计时器不同。

如果你真的更喜欢使用系统的计时器,那么你可以设置SynchronizingObject它来将它的事件编组到 UI 线程:

timer.SynchronizingObject = this;

请注意,UserControl 是一个可同步的对象。

于 2013-10-02T18:06:25.070 回答
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 参数列表的方法

于 2013-10-02T17:58:10.347 回答
0

您已经编写代码的最简单修复是将 Timer 的 SynchronizingObject 设置为 Form,因此计时器将在 UI 线程上运行。

于 2013-10-02T18:33:12.500 回答