在阻塞后台工作者和Application.DoEvents上

时间:2017-10-27 22:04:18

标签: c# multithreading backgroundworker blocking doevents

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

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

    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;
        }
    }
}

2 个答案:

答案 0 :(得分:0)

在StartServerGetIP_DoWork方法中有一个StartFTPServer方法。我假设如果要求取消,您不会检查该方法。同样的事情适用于您的GetIP方法。那些可能是你的阻挡点。如果您想确保实际取消作业,您需要定期检查是否已请求取消。所以我建议您使用StartFTPServer和GetIP的异步方法来检查后台工作者是否有请求取消。

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

答案 1 :(得分:0)

这是一种通过使用Microsoft的Reactive Framework(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);