Reputation: 1
I am trying to cancel a background worker if its currently running, and then start another.
I tried this first, there are more checks for cancel in the functions...
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);
}
This approach blocks if the worker is canceled on StartServerGetIP.RunWorkerAsync();
After this I found an ugly solution in
private void StartWorker()
{
if (StartServerGetIP.IsBusy) { StartServerGetIP.CancelAsync(); }
while(StartServerGetIP.IsBusy) { Application.DoEvents(); }
StartServerGetIP.RunWorkerAsync();
}
Is there a pattern I can implement that will allow me to async cancel the background worker and start another without calling Application.DoEvents
?
EDIT: A cancel button is out of the question.
EDIT: For those asking about the inner methods...
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;
}
}
Might as well give you the ring buffer too..
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;
}
}
}
Upvotes: 0
Views: 293
Reputation: 117037
Here's a simple way to effectively cancel an in-flight function that's operating on another thread by using Microsoft's Reactive Framework (Rx).
Start with a long-running function that returns the value you want:
Func<string> GetIP = () => ...;
And you have some sort of trigger - could be a button click or a timer, etc - or in my case I'm using a type from the Rx library.
Subject<Unit> trigger = new Subject<Unit>();
Then you can write this code:
IObservable<string> query =
trigger
.Select(_ => Observable.Start(() => GetIP()))
.Switch()
.ObserveOn(this);
IDisposable subscription =
query
.Subscribe(ip => { /* Do something with `ip` */ });
Now anytime that I want to initiate the function I can call this:
trigger.OnNext(Unit.Default);
If I initiate a new call while an existing call is running the existing call will be ignored and only the latest call (so long as it completes) will end up being produced by the query and the subscription will get it.
The query keeps running and responds to every trigger event.
The .ObserveOn(this)
(assuming you're using WinForms) brings the result back on to the UI thread.
Just NuGet "System.Reactive.Windows.Forms" to get the bits.
If you want trigger
to be a button click, do this:
IObservable<Unit> trigger =
Observable
.FromEventPattern<EventHandler, EventArgs>(
h => button.Click += h,
h => button.Click -= h)
.Select(_ => Unit.Default);
Upvotes: 0
Reputation: 149
In your StartServerGetIP_DoWork method there's a StartFTPServer method. I assume you don't check in that method if a cancellation has been requested. The same thing applies to your GetIP method. Those are probably your blocking points. If you want to ensure to actually cancel the job, you need to check periodically if a cancellation has been requested. So I would suggest you use an async method for StartFTPServer and GetIP that will check if the background worker has a cancellation requested.
I don't know the exact implementation you did in the StartFTPServer method or the GetIP method. If you would like more details on how to refactor the code so it can be cancelled post the code.
Upvotes: 0