【问题标题】:Cross Thread Operation - Invoked function calls another function C#跨线程操作 - 调用的函数调用另一个函数 C#
【发布时间】:2013-10-03 02:42:02
【问题描述】:

我有一个使用许多线程的程序,当其中一个线程找到答案时(我认为上下文并不重要) - 它宣布它,然后我创建的第一个线程调用用户控件中的函数使用调用的类。 我检查了 - 如果我更改此函数中的任何属性,我不会得到跨线程操作。但是这个函数启动了一个计时器(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() 被调用,但我在刻度函数中放置了一个消息框,但它没有进入那里所以我改变了它 - 我看到一些答案说它更好)

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

提前非常感谢! :)

【问题讨论】:

    标签: c# multithreading timer


    【解决方案1】:

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

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

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

    timer.SynchronizingObject = this;
    

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

    【讨论】:

    • 感谢您的回答。我不知道为什么,但是当我使用 Windows.Forms.Timer 时,程序无法进入刻度事件函数(我在那里放了一个消息框但它没有显示)。另外,当我使用 syncronizingObject 时,我没有得到跨线程操作,它在中间停止。 (我的程序绘制 - 将彩色图像 - 在图片框中。它应该将它们全部填充 - 如果我使用他在下面的评论中建议的调用 Dan Morphis 会这样做 - 但使用 synchronizingObject 它会因某种原因停止......)
    • @Odedg 要么你阻塞了 UI 线程,因此阻止了处理程序运行,你没有启动计时器,或者你没有添加正确的处理程序(或者可能更模糊的东西) .不看代码,无法真正给出具体细节。
    • (也请看我的评论,我编辑了它)。 UI线程是主线程,对吧?你能告诉我如何阻止它吗? (然后我会看看我是否这样做)。我正在使用自动重置事件在我的线程之间进行同步,它们是否也会中断我的主线程?
    • @Odedg 如果您让主线程对其他结果进行阻塞等待,那么是的,这是一个问题,您几乎肯定不应该这样做。
    • 我不确定我是否这样做了,我不是故意的...你能告诉我什么可能会阻止它,以便我知道在我的代码中搜索什么可能会导致问题?我正在使用的自动重置事件可以阻止它吗? (以及如何?)谢谢!
    【解决方案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 参数列表的方法

    【讨论】:

    • 谢谢!那行得通。 “=>”有什么作用?如果你能解释这整行就好了,MethodInvoker 和语法...谢谢:)
    • 感谢您的编辑,现在已经很清楚了,它可以工作了 :) 你知道为什么它不能与 Windows.Forms.Timer 一起工作吗(它没有到达 tick 事件函数)?
    • 如果不启动代码,我最初的猜测可能是时间没有开始,或者有什么东西阻止了计时器滴答事件。
    【解决方案3】:

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

    【讨论】:

    • 定时器在用户控件类中,你的意思是把定时器的SynchronizingObject设置为this =>到用户控件吗?而且,正如您在我在 cmets 中对 Servy 的回答所写的内容中所看到的那样, synchronizingObject 确实解决了跨线程操作,但由于某种原因,绘画在中间停止了(丹·莫菲斯的回答不会发生这种情况)。谢谢你:)
    • 是的,来自 Ui 的任何对象都可以用于 SynchronizingObject
    • 好的,谢谢。那么你知道为什么它会停在中间吗?
    • 没关系,问题出在我代码的其他地方。非常感谢 - 它现在可以工作了 :) 如果您知道为什么当我使用 Windows.Forms.Timer 时它没有达到我的计时器滴答功能(滴答事件),请告诉我 :)
    • 我相信你在这里得到的三个答案通常是正确的,所以你应该选择哪个是最好的,并通过点击签入来接受它作为答案....
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-19
    • 2019-07-08
    • 1970-01-01
    相关资源
    最近更新 更多