【问题标题】:On a blocking background worker and Application.DoEvents在阻塞的后台工作人员和 Application.DoEvents
【发布时间】:2017-10-27 22:04:17
【问题描述】:

如果后台工作程序当前正在运行,我正在尝试取消它,然后启动另一个。

我先尝试了这个,函数中有更多的取消检查...

    private void StartWorker()
    {
        if (StartServerGetIP.IsBusy) { StartServerGetIP.CancelAsync(); }
        StartServerGetIP.RunWorkerAsync();
    }
 private void StartServerGetIP_DoWork(object sender, DoWorkEventArgs e)
    {
        StartFTPServer(Port, Ringbuf, sender as BackgroundWorker, e);
        if ((sender as BackgroundWorker).CancellationPending) return;
        GetIP(Ringbuf, sender as BackgroundWorker, e);
    }

private void StartServerGetIP_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            return;
        }
        if (e.Result.ToString() == "Faulted")
        {
            tcs.SetResult(false);
            return;
        }

        Client.IPAddress = e.Result.ToString();
        tcs.SetResult(true);
    }

如果工人在StartServerGetIP.RunWorkerAsync();被取消,这种方法会阻塞

在此之后我在

中找到了一个丑陋的解决方案
        private void StartWorker()
    {
        if (StartServerGetIP.IsBusy) { StartServerGetIP.CancelAsync(); }
        while(StartServerGetIP.IsBusy) { Application.DoEvents(); }

        StartServerGetIP.RunWorkerAsync();
    }

是否有一种模式可以让我异步取消后台工作程序并启动另一个而不调用Application.DoEvents

编辑:取消按钮是不可能的。

编辑:对于那些询问内部方法的人......

private void StartFTPServer(SerialPort port, RingBuffer<string> buffer, BackgroundWorker sender, DoWorkEventArgs args)
    {
        Stopwatch timeout = new Stopwatch();
        TimeSpan max = TimeSpan.FromSeconds(MaxTime_StartServer);
        int time_before = 0;
        timeout.Start();
        while (!buffer.Return.Contains("Run into Binary Data Comm mode...") && timeout.Elapsed.Seconds < max.Seconds)
        {
            if (timeout.Elapsed.Seconds > time_before)
            {
                time_before = timeout.Elapsed.Seconds;
                sender.ReportProgress(CalcPercentage(max.Seconds, timeout.Elapsed.Seconds));
            }
            if (sender.CancellationPending)
            {
                args.Cancel = true;
                return;
            }
        }
        port.Write("q"); //gets into menu
        port.Write("F"); //starts FTP server
    }

        private void GetIP(RingBuffer<string> buffer, BackgroundWorker sender, DoWorkEventArgs args)
    {
        //if longer than 5 seconds, cancel this step
        Stopwatch timeout = new Stopwatch();
        TimeSpan max = TimeSpan.FromSeconds(MaxTime_GetIP);
        timeout.Start();
        int time_before = 0;
        string message;

        while (!(message = buffer.Return).Contains("Board IP:"))
        {
            if (timeout.Elapsed.Seconds > time_before)
            {
                time_before = timeout.Elapsed.Seconds;
                sender.ReportProgress(CalcPercentage(max.Seconds, timeout.Elapsed.Seconds + MaxTime_StartServer));
            }
            if (timeout.Elapsed.Seconds >= max.Seconds)
            {
                args.Result = "Faulted";
                return;
            }
            if (sender.CancellationPending)
            {
                args.Cancel = true;
                return;
            }
        }
        Regex regex = new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b");
        string IP = message.Remove(0, "Board IP: ".Length);
        if (regex.IsMatch(IP))
        {
            args.Result = IP;
            ServerAlive = true;
        }
    }

还不如给你环形缓冲区..

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FPGAProgrammerLib
{
    class RingBuffer<T>
    {
        T [] buffer { get; set; }

        int _index;
        int index
        {
            get
            {
                return _index;
            }
            set
            {
                _index = (value) % buffer.Length;
            }
        }


        public T Add
        {
            set
            {
                buffer[index++] = value;
            }
        }

        public T Return
        {
            get
            {
                return (index == 0) ? (IsString() ? (T)(object)string.Empty : default(T)) : buffer[--index];
            }
        }

        private bool IsString()
        {
            return (typeof(T) == typeof(string) || (typeof(T) == typeof(String)));
        }

        public RingBuffer(int size)
        {
            buffer = new T[size];
            index = 0;
        }
    }
}

【问题讨论】:

  • 永远不要使用Application.DoEvents()。它充满了危险。除非您非常小心/幸运,否则将导致极其难以调试代码中的错误。它只是在向后兼容 VB6 升级的框架中。
  • 有更好的方法来做到这一点。你能发布完整的代码吗?我希望能够复制、粘贴和运行您的代码。
  • 按照您现在的方式,它始终是一项基本竞赛。您无法保证 CancelAsync() 实际上确保不会设置 tcs.Result。也无法调试,这每周只会出错一次。没有足够的代码来提出一个正确的解决方案,你必须要么失败 StartWorker() 要么确保它永远不会被调用。

标签: c# multithreading backgroundworker blocking doevents


【解决方案1】:

在您的 StartServerGetIP_DoWork 方法中有一个 StartFTPServer 方法。如果已请求取消,我假设您不会签入该方法。这同样适用于您的 GetIP 方法。这些可能是您的障碍点。如果要确保实际取消作业,则需要定期检查是否已请求取消。所以我建议你对 StartFTPServer 和 GetIP 使用异步方法来检查后台工作人员是否有取消请求。

我不知道您在 StartFTPServer 方法或 GetIP 方法中所做的确切实现。如果您想了解有关如何重构代码的更多详细信息,以便可以在发布代码后取消。

【讨论】:

    【解决方案2】:

    这是一种使用 Microsoft 的反应式框架 (Rx) 有效取消在另一个线程上运行的运行中函数的简单方法。

    从返回所需值的长时间运行函数开始:

    Func<string> GetIP = () => ...;
    

    您有某种触发器 - 可能是按钮单击或计时器等 - 或者在我的情况下,我使用的是 Rx 库中的类型。

    Subject<Unit> trigger = new Subject<Unit>();
    

    那么你就可以写这段代码了:

    IObservable<string> query =
        trigger
            .Select(_ => Observable.Start(() => GetIP()))
            .Switch()
            .ObserveOn(this);
    
    IDisposable subscription =
        query
            .Subscribe(ip => { /* Do something with `ip` */ });
    

    现在,只要我想启动该函数,我就可以调用它:

    trigger.OnNext(Unit.Default);
    

    如果我在现有呼叫运行时发起新呼叫,则现有呼叫将被忽略,并且只有最新呼叫(只要完成)最终会由查询生成,并且订阅将获得它。

    查询继续运行并响应每个触发事件。

    .ObserveOn(this)(假设您使用的是 WinForms)将结果返回到 UI 线程。

    只需 NuGet "System.Reactive.Windows.Forms" 即可获取相关信息。

    如果您希望 trigger 成为按钮点击,请执行以下操作:

    IObservable<Unit> trigger =
        Observable
            .FromEventPattern<EventHandler, EventArgs>(
                h => button.Click += h,
                h => button.Click -= h)
            .Select(_ => Unit.Default);
    

    【讨论】:

      猜你喜欢
      • 2013-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多