0

スレッドの1つが答えを見つけたとき(コンテキストはあまり重要ではないと思います)、多くのスレッドを使用するプログラムがあります-それを発表し、作成した最初のスレッドが 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フォームタイマーを使用しましたが、何らかの理由でtick-event関数に到達しませんでした(timer.Start()が呼び出されましたが、tick関数にメッセージボックスを入れましたが、そこに入らなかったので変更しましたそれ - 私はそれがより良いと言っているいくつかの答えを見ました)

助けていただければ幸いです。長い投稿で申し訳ありません。できるだけ明確にしたかっただけです...

よろしくお願いします!:)

4

3 に答える 3

3

Windows.Forms.Timerの代わりに a を使用しSystem.Timers.Timerます。(いくつかのプロパティ/イベントの名前を のTick代わりに変更する必要がありますがElapsed、それは簡単です。)

フォームの名前空間の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));

=> はラムダの構文です (他の言語では匿名メソッドと呼ばれます)。1行の関数と考えてください。

() => pencils[currIndex--].Image = image

以下と同じです:

void SetImage(Image image, ref int currIndex) {
    pencils[currIndex--].Image = image;
}

MethodInvokerは、void パラメーター リストを使用してメソッドを呼び出すために使用される単純なデリゲートを提供します。

于 2013-10-02T17:58:10.347 に答える
0

既にコードを作成しているので、タイマーの SynchronizingObject を Form に設定して、タイマーが UI スレッドで実行されるようにするのが最も簡単な修正方法です。

于 2013-10-02T18:33:12.500 に答える