Waiting for an event to fire while using SemaphoreSlim - c#

I can use a SemaphoreSlim to wait for an even to fire like this:
public class MyClass
{
private SemaphoreSlim _signal;
private ObjectThatHasEvent _object;
public MyClass()
{
_signal = new SemaphoreSlim(0, 1);
_object = new ObjectThatHasEvent();
_object.OnEventFired += _object_OneEventFired;
}
public asnyc void Run()
{
_object.DoStuffAndFireEventAfterwards();
_signal.WaitAsync();
}
private void _object_OnEventFired(object sender, EventArgs e)
{
_signal.Release();
}
}
But what about if I need to wait for an event from _object to finish first and then another event before calling _signal.Release()? Like so:
public class MyClass
{
private SemaphoreSlim _signal;
private ObjectThatHasEvent _object;
public MyClass()
{
_signal = new SemaphoreSlim(0, 1);
_object = new ObjectThatHasEvent();
_object.OnConnected += _object_OnConnected;
_object.OnWorkFinished += _object_OnWorkFinished;
_object.OnDisconnected += _object_OnDisconnected;
}
public async Task Run()
{
_object.Connect();
await _signal.WaitAsync();
}
private void _object_OnConnected(object sender, EventArgs e)
{
_object.DoWork();
//How to wait for work finished here?
_object.Disconnect();
}
private void _object_OnWorkFinished(object sender, EventArgs e)
{
//Only disconnect after this has finished...
}
private void _object_OnDisconnected(object sender, EventArgs e)
{
_signal.Release();
}
}

Using SemaphoreSlim for a signal is possible, but the more common pattern is to use TaskCompletionSource<T> to make the events async-friendly (i.e., TAP). Once you have async-friendly methods, you can combine them much more naturally.
I prefer to write my TAP wrappers as extension methods, something like:
public static class ObjectThatHasEventExtensions
{
public static Task ConnectAsync(this ObjectThatHasEvent self)
{
// TODO: this wrapper does not handle connection errors.
var tcs = new TaskCompletionSource<object>();
EventHandler handler = null;
handler = (sender, args) =>
{
self.OnConnected -= handler;
tcs.TrySetResult(null);
};
self.OnConnected += handler;
self.Connect();
return tcs.Task;
}
public static Task DoWorkAsync(this ObjectThatHasEvent self)
{
// TODO: this wrapper does not handle work errors.
var tcs = new TaskCompletionSource<object>();
EventHandler handler = null;
handler = (sender, args) =>
{
self.OnWorkFinished -= handler;
tcs.TrySetResult(null);
};
self.OnWorkFinished += handler;
self.DoWork();
return tcs.Task;
}
// (same pattern for DisconnectAsync)
}
Once you have TAP extension methods, composing them is much, much easier:
public class MyClass
{
private ObjectThatHasEvent _object;
public MyClass()
{
_object = new ObjectThatHasEvent();
}
public async Task Run()
{
await _object.ConnectAsync();
await _object.DoWorkAsync();
await _object.DisconnectAsync();
}
}

As per my comment, would something like this work?
public class MyClass
{
private SemaphoreSlim _signal;
private ObjectThatHasEvent _object;
public MyClass()
{
_signal = new SemaphoreSlim(2, 2);
_object = new ObjectThatHasEvent();
_object.OnConnected += _object_OnConnected;
_object.OnWorkFinished += _object_OnWorkFinished;
_object.OnDisconnected += _object_OnDisconnected;
}
public async Task Run()
{
if (_signal.CurrentCount == 2) // Make sure no other connections exist (still 2 threads available)
{
await _signal.WaitAsync();
_object.Connect();
}
}
private void _object_OnConnected(object sender, EventArgs e)
{
if (_signal.CurrentCount == 1) //Only do work if we've connected
{
await _signal.WaitAsync();
_object.DoWork();
_object.Disconnect();
}
}
private void _object_OnWorkFinished(object sender, EventArgs e)
{
_signal.Release();
}
private void _object_OnDisconnected(object sender, EventArgs e)
{
_signal.Release();
}
}

Related

C# form, how to stop a thread with a button?

I am doing a form application in C#, it's a client that receives string with socket from server, I have a thread which runs an endless loop with inside the receive function. When I click a button I have to stop this thread, I have tried with a boolean variable but it doesn't work because the function get stuck on the receive function and the control on the boolean variable is done before when is still true. How Can I do to stop this thread?
This is the function which is runned by the thread:
public void Prova()
{
while (true)
{
string str = frmRegister.c.Receive();
... doing things...
}
}
The thread is started in this way:
public Form1()
{
InitializeComponent();
t1 = new Thread(Prova);
t1.Start();
}
And I have to stop the thread here:
private void goBack_Click(object sender, EventArgs e)
{
new Form2().Show();
this.Hide();
}
The following is my attempt with the CancellationToken:
private CancellationTokenSource tokenSource;
public void Prova(CancellationToken token)
{
while(!token.IsCancellationRequested){
while (true)
{
string str = frmRegister.c.Receive();
... doing things...
}
}
}
public Form1()
{
InitializeComponent();
tokenSource = new CancellationTokenSource();
var task = Task.Run(() => Prova(tokenSource.Token));
}
private void goBack_Click(object sender, EventArgs e)
{
tokenSource.Cancel();
new Form2().Show();
this.Hide();
}
Given your sample this should work:
private CancellationTokenSource tokenSource;
public void Prova(CancellationToken token = default)
{
while(!token.IsCancellationRequested) {
string str = frmRegister.c.Receive();
... doing things...
}
}
}
Or even:
public void Prova(CancellationToken token = default)
{
while (true)
{
token.ThrowIfCancellationRequested();
string str = frmRegister.c.Receive();
... doing things...
}
}
Or:
public void Prova(CancellationToken token = default)
{
while (true)
{
if(token.IsCancellationRequested)
return;
string str = frmRegister.c.Receive();
... doing things...
}
}
Basically get rid of the double while loop and it works.
Why did you use Thread instead of Task?
In your case
Thread th;
CancellationTokenSource cts = new CancellationTokenSource();
public void Prova(CancellationToken cancellationToken)
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
string str = frmRegister.c.Receive();
... doing things...
}
}
public Form1()
{
InitializeComponent();
th = new Thread(() => { Prova(cts.Token); });
th.Start();;
}
private void goBack_Click(object sender, EventArgs e)
{
cts.Cancel();
}
With task
Task th;
CancellationTokenSource cts = new CancellationTokenSource();
public void Prova(CancellationToken cancellationToken)
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
...process
}
}
public Form1()
{
InitializeComponent();
th = Task.Run(() => { Prova(cts.Token); }, cts.Token);
th.Start(); ;
}
private void goBack_Click(object sender, EventArgs e)
{
cts.Cancel();
}

Wait inside While with ManualResetEvent

I have an operation via While that i want to Freeze:
do
{
ManualResetEvent.Reset(); // Here i want to wait
// Here i am doing my stuff...
}
while (some boolean value);
}
My ManualResetEvent :
private static ManualResetEvent _manualResetEvent;
public static ManualResetEvent ManualResetEvent
{
get { return _manualResetEvent; }
set { _manualResetEvent = value; }
}
ManualResetEvent = new ManualResetEvent(false);
In some point in my code via Button i just want to freeze my operation:
private void btnPause_Click(object sender, RoutedEventArgs e)
{
ManualResetEvent.WaitOne();
}
Is this the right way to do that ?
You have your two functions backwards. The loop you want to wait needs to use the .WaitOne(). Also, if you want it to run at the start you need to initialize the reset event to true
init
private static ManualResetEvent _manualResetEvent;
public static ManualResetEvent ManualResetEvent
{
get { return _manualResetEvent; }
set { _manualResetEvent = value; }
}
ManualResetEvent = new ManualResetEvent(true); //Set it to true to let it run at the start.
loop
do
{
ManualResetEvent.WaitOne(); // Here i want to wait
// Here i am doing my stuff...
}
while (some boolean value);
}
elsewhere
private void btnPause_Click(object sender, RoutedEventArgs e)
{
ManualResetEvent.Reset();
}
private void btnUnPause_Click(object sender, RoutedEventArgs e)
{
ManualResetEvent.Set();
}

Using BackgroundWorker to complete two methods one after the other WPF/C#

In my program I have two methods that takes a while to complete, about few minutes each. While these methods are being executed, I display a Progress Bar in a separate window which shows the progress of each method. My two methods are in a static Utility class. They look like the following:
public static class Utility
{
public static bool TimeConsumingMethodOne(object sender)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100);
(sender as BackgroundWorker).ReportProgress(i);
}
return true;
}
public static bool TimeConsumingMethodTwo(object sender)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(50);
(sender as BackgroundWorker).ReportProgress(i);
}
return true;
}
}
Reading through similar questions in SO I learned that I should use BackgroundWorker and used the RunWorkerCompleted() to see when the worker completes its work. So in my Main() I used BackgroundWorer() and subscribed to the RunWorkerCompleted() method. My goal here is to run the TimeConsumingMethodOne() first (and display progress while running), then once finished, run TimeConsumingMethodTwo() and show progress again, and when that's completed output the message box (which simulates some other work in my program). My Main() looks like the following:
public partial class MainWindow : Window
{
public enum MethodType
{
One,
Two
}
private BackgroundWorker worker = null;
private AutoResetEvent _resetEventOne = new AutoResetEvent(false);
private AutoResetEvent _resetEventTwo = new AutoResetEvent(false);
private ProgressBarWindow pbWindowOne = null;
private ProgressBarWindow pbWindowTwo = null;
public MainWindow()
{
InitializeComponent();
}
private void btnRun_Click(object sender, RoutedEventArgs e)
{
RunMethodCallers(sender, MethodType.One);
_resetEventOne.WaitOne();
RunMethodCallers(sender, MethodType.Two);
_resetEventTwo.WaitOne();
MessageBox.Show("COMPLETED!");
}
private void RunMethodCallers(object sender, MethodType type)
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
switch (type)
{
case MethodType.One:
worker.DoWork += MethodOneCaller;
worker.ProgressChanged += worker_ProgressChangedOne;
worker.RunWorkerCompleted += worker_RunWorkerCompletedOne;
break;
case MethodType.Two:
worker.DoWork += MethodTwoCaller;
worker.ProgressChanged += worker_ProgressChangedTwo;
worker.RunWorkerCompleted += worker_RunWorkerCompletedTwo;
break;
}
worker.RunWorkerAsync();
}
private void MethodOneCaller(object sender, DoWorkEventArgs e)
{
Dispatcher.Invoke(() =>
{
pbWindowOne = new ProgressBarWindow("Running Method One");
pbWindowOne.Owner = this;
pbWindowOne.Show();
});
Utility.TimeConsumingMethodOne(sender);
}
private void MethodTwoCaller(object sender, DoWorkEventArgs e)
{
Dispatcher.Invoke(() =>
{
pbWindowTwo = new ProgressBarWindow("Running Method Two");
pbWindowTwo.Owner = this;
pbWindowTwo.Show();
});
Utility.TimeConsumingMethodTwo(sender);
}
private void worker_RunWorkerCompletedOne(object sender, RunWorkerCompletedEventArgs e)
{
_resetEventOne.Set();
}
private void worker_RunWorkerCompletedTwo(object sender, RunWorkerCompletedEventArgs e)
{
_resetEventTwo.Set();
}
private void worker_ProgressChangedOne(object sender, ProgressChangedEventArgs e)
{
pbWindowOne.SetProgressUpdate(e.ProgressPercentage);
}
private void worker_ProgressChangedTwo(object sender, ProgressChangedEventArgs e)
{
pbWindowTwo.SetProgressUpdate(e.ProgressPercentage);
}
}
Now the problem I have is, when I use _resetEventOne.WaitOne(); the UI hangs. If I removed those two waits, both methods run asynchronously and the execution moves on and outputs the MessageBox even before those two methods complete.
What am I doing wrong? How do I get the program to finish my first BackgroundWorker and then move onto the next, and then when that's done, output the MessageBox?
Now the problem I have is, when I use _resetEventOne.WaitOne(); the UI hangs. If I removed those two waits, both methods run asynchronously and the execution moves on and outputs the MessageBox even before those two methods complete.
What am I doing wrong?
When you call WaitOne(), you are blocking the UI thread, causing the UI to hang. If you remove that call, then of course you start both workers at once.
There are several different ways to approach your question. One is to stick as closely to your current implementation, and just fix the barest minimum to get it to work. Doing that, what you'll need to do is perform the actual next statement in the RunWorkerCompleted handler, instead of using an event to wait for the handler to execute.
That looks like this:
public partial class MainWindow : Window
{
public enum MethodType
{
One,
Two
}
private BackgroundWorker worker = null;
private ProgressBarWindow pbWindowOne = null;
private ProgressBarWindow pbWindowTwo = null;
public MainWindow()
{
InitializeComponent();
}
private void btnRun_Click(object sender, RoutedEventArgs e)
{
RunMethodCallers(sender, MethodType.One);
}
private void RunMethodCallers(object sender, MethodType type)
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
switch (type)
{
case MethodType.One:
worker.DoWork += MethodOneCaller;
worker.ProgressChanged += worker_ProgressChangedOne;
worker.RunWorkerCompleted += worker_RunWorkerCompletedOne;
break;
case MethodType.Two:
worker.DoWork += MethodTwoCaller;
worker.ProgressChanged += worker_ProgressChangedTwo;
worker.RunWorkerCompleted += worker_RunWorkerCompletedTwo;
break;
}
worker.RunWorkerAsync();
}
private void MethodOneCaller(object sender, DoWorkEventArgs e)
{
Dispatcher.Invoke(() =>
{
pbWindowOne = new ProgressBarWindow("Running Method One");
pbWindowOne.Owner = this;
pbWindowOne.Show();
});
Utility.TimeConsumingMethodOne(sender);
}
private void MethodTwoCaller(object sender, DoWorkEventArgs e)
{
Dispatcher.Invoke(() =>
{
pbWindowTwo = new ProgressBarWindow("Running Method Two");
pbWindowTwo.Owner = this;
pbWindowTwo.Show();
});
Utility.TimeConsumingMethodTwo(sender);
}
private void worker_RunWorkerCompletedOne(object sender, RunWorkerCompletedEventArgs e)
{
RunMethodCallers(sender, MethodType.Two);
}
private void worker_RunWorkerCompletedTwo(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("COMPLETED!");
}
private void worker_ProgressChangedOne(object sender, ProgressChangedEventArgs e)
{
pbWindowOne.SetProgressUpdate(e.ProgressPercentage);
}
private void worker_ProgressChangedTwo(object sender, ProgressChangedEventArgs e)
{
pbWindowTwo.SetProgressUpdate(e.ProgressPercentage);
}
}
That said, BackgroundWorker has been made obsolete by the newer task-based API with async and await. With some small changes to your code, it can be adapted to use that newer idiom:
public partial class MainWindow : Window
{
public enum MethodType
{
One,
Two
}
private ProgressBarWindow pbWindowOne = null;
private ProgressBarWindow pbWindowTwo = null;
public MainWindow()
{
InitializeComponent();
}
private async void btnRun_Click(object sender, RoutedEventArgs e)
{
await RunMethodCallers(sender, MethodType.One);
await RunMethodCallers(sender, MethodType.Two);
MessageBox.Show("COMPLETED!");
}
private async Task RunMethodCallers(object sender, MethodType type)
{
IProgress<int> progress;
switch (type)
{
case MethodType.One:
progress = new Progress<int>(i => pbWindowOne.SetProgressUpdate(i));
await Task.Run(() => MethodOneCaller(progress));
break;
case MethodType.Two:
progress = new Progress<int>(i => pbWindowTwo.SetProgressUpdate(i));
await Task.Run(() => MethodTwoCaller(progress));
break;
}
}
private void MethodOneCaller(IProgress<int> progress)
{
Dispatcher.Invoke(() =>
{
pbWindowOne = new ProgressBarWindow("Running Method One");
pbWindowOne.Owner = this;
pbWindowOne.Show();
});
Utility.TimeConsumingMethodOne(progress);
}
private void MethodTwoCaller(IProgress<int> progress)
{
Dispatcher.Invoke(() =>
{
pbWindowTwo = new ProgressBarWindow("Running Method Two");
pbWindowTwo.Owner = this;
pbWindowTwo.Show();
});
Utility.TimeConsumingMethodTwo(progress);
}
}
To do the above does require a small adjustment to the Utility class as well:
static class Utility
{
public static bool TimeConsumingMethodOne(IProgress<int> progress)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100);
progress.Report(i);
}
return true;
}
public static bool TimeConsumingMethodTwo(IProgress<int> progress)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(50);
progress.Report(i);
}
return true;
}
}
That is, the Progress<T> class takes the place of the BackgroundWorker.ProgressChanged event and ReportProgress() method.
Note that with the above, the code has gotten significantly shorter, simpler, and is written in a more direct way (i.e. related statements are with each other in the same method now).
The example you gave is necessarily simplified. That's perfectly fine, but it does mean that it's not known here what the Thread.Sleep() method represents. In fact, in many cases, this sort of thing can be refactored further such that only the long-running work is done asynchronously. This can sometimes simplify the progress-reporting even further, because it can be done after await-ing each individual asynchronously-executed work component.
For example, let's suppose the work in the loop is either inherently asynchronous or is costly enough that it's reasonable to use Task.Run() to execute each loop iteration. For the purpose of the same, that can be represented using Task.Delay():
static class Utility
{
public static async Task<bool> TimeConsumingMethodOne(Action<int> progress)
{
for (int i = 1; i <= 100; i++)
{
await Task.Delay(100);
progress(i);
}
return true;
}
public static async Task<bool> TimeConsumingMethodTwo(Action<int> progress)
{
for (int i = 1; i <= 100; i++)
{
await Task.Delay(50);
progress(i);
}
return true;
}
}
In the above, I also don't use Progress<T>. Just a simple Action<int> delegate for the caller to use however they want.
And with that change, your window code gets even simpler:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void btnRun_Click(object sender, RoutedEventArgs e)
{
await MethodOneCaller();
await MethodTwoCaller();
MessageBox.Show("COMPLETED!");
}
private async Task MethodOneCaller()
{
ProgressBarWindow pbWindowOne =
new ProgressBarWindow("Running Method One") { Owner = this };
pbWindowOne.Show();
await Utility.TimeConsumingMethodOne(i => pbWindowOne.SetProgressUpdate(i));
}
private async Task MethodTwoCaller()
{
ProgressBarWindow pbWindowTwo =
new ProgressBarWindow("Running Method Two") { Owner = this };
pbWindowTwo.Show();
await Utility.TimeConsumingMethodTwo(i => pbWindowTwo.SetProgressUpdate(i));
}
}
Granted, I took the opportunity to remove the MethodType enum and just call the methods directly, which shortened the code even more. But even if all you did was avoid the use of Dispatcher.Invoke(), that still simplifies the code a lot.
In addition to all that, if you were using data binding to represent the progress state instead of setting the value directly, WPF would handle the cross-thread invocation implicitly for you, so that the Progress<T> class isn't even required even if you can't refactor the Utility class code for it itself to be async.
But, those are minor refinements compared to moving away from BackgroundWorker. I recommend doing that, but whether you invest time in those further refinements is less important.
An option i prefer is to have those 2 methods in a different thread and use a while loop to check if thread is still running and if it is use Task.Delay()
EG.
private async void BlahBahBlahAsync()
{
Thread testThread = new Thread(delegate () { });
newThread = new Thread(delegate ()
{
Timeconsuming();
});
newThread.Start();
while (testThread.IsAlive)
{
await Task.Delay(50);
}
}
private void Timeconsuming()
{
// stuff that takes a while
}

C# WPF Exiting application with running background workers which update dialog

My question is similar to this one, I have pretty much the same code setup except I'm using BackgroundWorker instead of WorkflowRuntime. (And the answer doesn't appear to work for me)
In the past I have used Application.Current.Shutdown(); in the closing event of MainWindow, however I was hoping that by properly disposing of this window which I've made a static resource I could perhaps not need that.
The problem is that if I exit via closing MainWindow after all the background tasks terminate an empty BackgroundDialog remains open.
public partial class BackgroundDialog : Window
{
private static BackgroundDialog _Dialog = new BackgroundDialog();
private static UIElementCollection TasksView { get { return _Dialog.BackgroundList.Children; } }
public static void Add(BackgroundItem item)
{
if (TasksView.Count == 0)
{
_Dialog.Show();
}
TasksView.Add(item);
}
public static void Remove(BackgroundItem item)
{
TasksView.Remove(item);
if (TasksView.Count == 0)
{
if (_Release)
{
FinishRelease();
}
else
{
_Dialog.Hide();
}
}
}
private static bool _Release = false;
private static void FinishRelease()
{
// FinishRelease may be called from a BackgroundWorker thread finishing
// This results in _Dialog.Close() not behaving as expected
// For more details: https://stackoverflow.com/questions/5659930/wpf-window-not-closing
_Dialog.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
_Dialog.Close();
_Dialog = null;
}));
}
public static void Release(EventArgs e)
{
_Release = true;
if (TasksView.Count == 0)
{
FinishRelease();
}
else foreach (BackgroundItem Task in TasksView)
{
Task.Abort();
}
}
}
public partial class BackgroundItem : UserControl
{
public delegate void TaskHandler(BackgroundWorker Worker);
public interface IBackgroundTask
{
bool IsIndeterminate { get; }
int MaxProgress { get; }
string Title { get; }
string Description(int progress);
TaskHandler Exec { get; }
}
private BackgroundWorker Worker;
public BackgroundItem(IBackgroundTask task)
{
InitializeComponent();
Title.Text = task.Title;
Description.Text = task.Description(0);
Progress.Value = 0;
Progress.Minimum = 0;
Progress.Maximum = task.MaxProgress;
Progress.IsIndeterminate = task.IsIndeterminate;
BackgroundDialog.Add(this);
Worker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true,
};
Worker.DoWork += (object sender, DoWorkEventArgs e) =>
{
task.Exec?.Invoke(Worker);
};
Worker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) =>
{
BackgroundDialog.Remove(this);
};
Worker.ProgressChanged += (object sender, ProgressChangedEventArgs e) =>
{
Progress.Value = e.ProgressPercentage;
Description.Text = task.Description(e.ProgressPercentage);
};
Worker.RunWorkerAsync();
Stop.Click += (object sender, RoutedEventArgs e) =>
{
Abort();
};
}
public void Abort()
{
Worker.CancelAsync();
Stop.IsEnabled = false;
StopText.Text = "Stopping";
}
}
public partial class MainWindow : Window
{
private class MyTask : BackgroundItem.IBackgroundTask
{
public bool IsIndeterminate => true;
public int MaxProgress => 100;
public string Title => "I'm Counting";
public BackgroundItem.TaskHandler Exec => (BackgroundWorker Worker) =>
{
for (int i = 0; i < 100; ++i)
{
if (Worker.CancellationPending)
{
break;
}
Worker.ReportProgress(i);
Thread.Sleep(500);
}
};
public string Description(int progress)
{
return progress.ToString();
}
}
public MainWindow()
{
InitializeComponent();
Loaded += (object sender, RoutedEventArgs e) => {
new BackgroundItem(new MyTask());
new BackgroundItem(new MyTask());
new BackgroundItem(new MyTask());
};
}
protected override void OnClosed(System.EventArgs e)
{
base.OnClosed(e);
BackgroundDialog.Release(e);
}
}
Try looking into Application.ShutdownMode. You'll want to set ShutdownMode to be OnMainWindowClose.
I feel silly, must have been the end of the day on Friday....here was the problem
in BackgroundDialog:
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
}
Must have been a relic from before I found this solution. However, some cancellation is needed to prevent the user from closing the dialog from the taskbar. So I wrapped the cancel with the statement if (!_Release)

How do I stop a script loaded with CSScript?

I run user defined scripts in my WPF application using CS-Script library. How can I cancel a script if it runs endless? As my users write the script I can't rely on a cancel flag that is checked inside the script.
Here is a simplified code snippet showing the problem:
public partial class MainWindow : Window
{
public string MessageFromScript
{
get { return (string)GetValue(MessageFromScriptProperty); }
set { SetValue(MessageFromScriptProperty, value); }
}
public static readonly DependencyProperty MessageFromScriptProperty =
DependencyProperty.Register("MessageFromScript", typeof(string), typeof(MainWindow), new PropertyMetadata(null));
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
BackgroundWorker worker = null;
private void OnStart(object sender, RoutedEventArgs e)
{
if(worker != null)
{
return;
}
worker = new BackgroundWorker();
worker.DoWork += RunScript;
worker.RunWorkerCompleted += ScriptCompleted;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync();
}
private void ScriptCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Cancelled)
MessageFromScript = "Script cancelled";
else
MessageFromScript = e.Result.ToString();
}
private void RunScript(object sender, DoWorkEventArgs e)
{
dynamic script = CSScript.Evaluator.LoadCode(#"using System;
using System.Threading;
public class Script
{
public string Test()
{
{int count=0; while(true) { count++; Console.WriteLine(count.ToString()); Thread.Sleep(200); }}
return ""Message from script"";
}
}");
e.Result = script.Test();
}
private void OnStop(object sender, RoutedEventArgs e)
{
if(worker == null)
{
return;
}
//TODO: How do I stop the script here?
worker = null;
}
}
In your Test() method add a parameter where you pass a CancellationToken to the script. Then design the loops in your script to check the canellation token if abort has been requested and break out. To stop the script just call the Cancel() method of your CancellationTokenSource which token you passed to the script on invocation.

Categories

Resources