【问题标题】:Run a method at a specified interval c#以指定的时间间隔运行方法c#
【发布时间】:2015-01-14 15:06:03
【问题描述】:

我有一个按钮,当按下该按钮时,可以获取远程 PC 上各种 Windows 服务的状态。我想每分钟自动刷新一次此按钮,以便始终显示服务的最新状态。

我试图设置一个计时器,但我不断收到错误消息“跨线程操作无效:控制 'btnRefreshServices' 从创建它的线程以外的线程访问”

任何帮助表示赞赏

    private void btnRefreshServices_Click(
        object sender,
        EventArgs eventArgs)
    {
        this.btnRefreshServices.Enabled = false;

        // Setting up progress bar in a separate thread to update the progress bar
        // This is necessary so that the dialog doesn't freeze while the progress bar is reporting its progress
        this.prgbServiceStatus.Minimum = 1;
        this.prgbServiceStatus.Maximum = 11;
        this.prgbServiceStatus.Step = 1;
        this.prgbServiceStatus.Value = 1;
        this.prgbServiceStatus.Increment(1);
        this.prgbServiceStatus.PerformStep();

        var _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.ProgressChanged += ProgressChanged;
        _backgroundWorker.DoWork += DoWork;
        _backgroundWorker.WorkerReportsProgress = true;
        _backgroundWorker.RunWorkerAsync();
        _backgroundWorker.RunWorkerCompleted += new  RunWorkerCompletedEventHandler(RunWorkerCompleted);
    }

    private void DoWork(
        object sender,
        DoWorkEventArgs doWorkEventArgs)
    {
        // Get the current status of each Windows service and reflect the progress in the progress bar
        // NOTE: If you add a new service, divide the number of services by 100 and update each call to report progress
        ((BackgroundWorker)sender).ReportProgress(15);

        CurrentStatus(
            this.grdResults,
            ServerA,
            ServiceName,
            RowIndexA);
        ((BackgroundWorker)sender).ReportProgress(25);

        CurrentStatus(
            this.grdResults,
            ServerB,
            ServiceNameB,
            RowIndexB);
        ((BackgroundWorker)sender).ReportProgress(35);

}

我在计时器上使用了类似这样的代码

    Timer myTimer = new Timer();
    myTimer.Elapsed += new ElapsedEventHandler(DisplayTimeEvent);
    myTimer.Interval = 1000; // 1000 ms is one second
    myTimer.Start();

    public static void DisplayTimeEvent(object source, ElapsedEventArgs e)
    {
       // code here will run every second
    }

使用 Emile Pels 代码,我能够解决我的问题。

    public frmServicesManager()
    {
        InitializeComponent();

        // The interval in milliseconds (1000 ms = 1 second)
        const double interval = 5000.0;

        // Create a new timer
        new System.Timers.Timer()
            {
                Enabled = true,
                Interval = interval
            }.Elapsed += TimerCallback;
    }

    private void TimerCallback(
        object sender, 
        ElapsedEventArgs elapsedEventArgs)
    {
        // SignalTime is now of type DateTime and contains the value indicating when the timer's Elapsed event was raised
        var _signalTime = elapsedEventArgs.SignalTime;

        // Create a new Action
        var _setButtonClick = new Action<DateTime>(dateTime => this.btnRefreshServices.PerformClick());

        // Check if we can access the control from this thread
        if (this.btnRefreshServices.InvokeRequired)
        {
            // We can't access the label from this thread,so we'll call invoke so it is executed from the thread the it was created on
            this.btnRefreshServices.Invoke(_setButtonClick, _signalTime);
        }
    }

【问题讨论】:

标签: c# multithreading user-interface webforms


【解决方案1】:

使用System.Windows.Forms.Timer,或者像这样从另一个线程设置按钮的属性:

myButton.Invoke(new Action<string>((text) => myButton.Text = text), "New button text");

编辑:这是一个有更多解释的示例。

您收到该错误的原因是您正在尝试访问在其他线程上创建的控件,这将不起作用。您必须调用控件的Invoke() 方法;执行您在创建控件的线程上传递的委托。 您可以使用的代表之一是Action,我将在本文后面进行演示。

对于以下示例,我使用了System.Timers.Timer,并创建了一个新的 Winforms 项目并仅添加了一个Label。它的名字是timeLabel。

我将此代码放在表单的构造函数中:

//The interval in milliseconds (1000 ms = 1 second)
const double interval = 1000.0;

//Create a new timer
new System.Timers.Timer()
{
    Enabled = true, //Start it right away
    Interval = interval //Set the interval
}.Elapsed += TimerCallback; //Register a handler for the elapsed event

这会创建一个新的计时器并注册一个回调来处理它的 Elapsed 事件,定义如下:

private void TimerCallback(object sender, System.Timers.ElapsedEventArgs e)
{
    const string baseString = "The event was raised at {0}";

    //signalTime is now of type DateTime and contains the value 
    //indicating when the timer's Elapsed event was raised
    var signalTime = e.SignalTime;

    //Create a new Action - delegate - which takes a string argument
    var setLabelText = new Action<DateTime>(dt =>
    {
        //If the amount of seconds in the dt argument is an even number,
        //set the timeLabel's forecolor to red; else, make it green
        timeLabel.ForeColor = dt.Second % 2 == 0 ? Color.Red : Color.Green;

        //Format the baseString to display the time in dt
        timeLabel.Text = string.Format(baseString, dt.ToLongTimeString());
    });

    //Check if we can access the control from this thread
    if (timeLabel.InvokeRequired) {
        //We can't access the label from this thread,
        //so we'll call invoke so it is executed from
        //the thread the it was created on
        timeLabel.Invoke(setLabelText, signalTime);
    }
    else {
        //The label's text can be set from this thread,
        //we'll just call the delegate without Invoke()
        setLabelText(signalTime);
    }
}

这个特殊的例子每秒将标签的文本更改为当前时间,如果秒数是偶数,它会使标签的前景色变为红色:当它是奇数时,颜色将设置为绿色。该程序可能看起来不是很有用,但它演示了如何从其他线程访问控件;一旦你掌握了这个例子,它应该可以帮助你扩展它以满足你的需要。

【讨论】:

  • 感谢 Emile Pels,将尝试一下
  • 说实话,Emile 我不太明白你上面的代码。这只是更改按钮上的文本。
  • @JohnS 嗨,约翰 - 我在帖子中添加了额外的解释和示例。
  • 非常感谢埃米尔,非常感谢
【解决方案2】:

我不清楚BackgroundWorker 代码与您的问题有什么关系。它似乎与跨线程问题无关,也与您定期刷新按钮无关。

就问题似乎是什么而言,您应该能够使用正确的Timer 类(.NET 中至少有三个)System.Windows.Forms.Timer。在这种情况下,您的代码将如下所示:

System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
myTimer.Tick += DisplayTimeEvent;
myTimer.Interval = 1000; // 1000 ms is one second
myTimer.Start();

public static void DisplayTimeEvent(object source, EventArgs e)
{
   // code here will run every second
}

上面的代码应该在 Winforms 模块中,因此 System.Windows.Forms 命名空间应该已经在范围内,但为了清楚起见,我已经完全限定了上面的 Timer 类名。

还要注意事件名称不同:Tick 而不是 Elapsed。并且事件处理程序签名略有不同。

使用这个Timer 类而不是您使用的那个,Tick 事件处理程序将在 UI 线程上调用,完全避免任何跨线程问题。

【讨论】:

    猜你喜欢
    • 2015-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-04
    • 1970-01-01
    相关资源
    最近更新 更多