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

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

Related

Execution of tasks launched asynchronously by button sequentially

It is necessary to execute the methods sequentially in the order they were started, but without stopping the UI. In the example that I made, the operations are performed asynchronously, which leads to incorrect entries in the ListNumber list.
public Form1()
{
InitializeComponent();
ListNumber = new List<string>();
}
List<string> ListNumber { get; set; }
private async void button1_Click(object sender, EventArgs e)
{
textBox1.Text = await Task.Run(() => MessageAsync());
}
private async Task<string> MessageAsync()
{
var concat = "";
await NumberAsync();
foreach (string number in ListNumber)
{
concat += number + ", ";
}
return concat;
}
private async Task NumberAsync()
{
for(int i = 0; i < 30; i++)
{
ListNumber.Add(i.ToString());
await Task.Delay(300);
}
}
If you quickly click on the button, the calling method gives the following result:
the result of the program
Xerillio's proposed solution does work as long as you don't expect the button to be responsive after be pressed:
private async void button1_Click(object sender, EventArgs e)
{
button1.IsEnabled = false;
textBox1.Text = await Task.Run(() => MessageAsync());
button1.IsEnabled = true;
}
If you need to be able to use the button while your task is running, or in other words you expect several things to need access to the ListNumber resource you need to design a system for controlling access to that resource. Only allowing one producer to add values to the list at a time for instance would be a method but it all depends on what behavior you want to see.
Below is a working version which controls access to the LisNumber object using a semaphore.
public MainWindow()
{
InitializeComponent();
ListNumber = new List<string>();
semaphore = new SemaphoreSlim(1, 1);
}
SemaphoreSlim semaphore;
List<string> ListNumber { get; set; }
private async void button1_Click(object sender, EventArgs e)
{
await NumberAsync();
textBox1.Text = await Message();
}
private async Task<string> Message()
{
await semaphore.WaitAsync();
var concat = "";
foreach (string number in ListNumber)
{
concat += number + ", ";
}
semaphore.Release();
return concat;
}
private async Task NumberAsync()
{
await semaphore.WaitAsync();
for (int i = 0; i < 30; i++)
{
ListNumber.Add(i.ToString());
await Task.Delay(300);
}
semaphore.Release();
}
You could also just wrap the button call in the semaphore if you wanted:
private async void button1_Click(object sender, EventArgs e)
{
await semaphore.WaitAsync();
await NumberAsync();
textBox1.Text = await Message();
semaphore.Release();
}

The async-await syntax is not working synchronously as I expected in C#

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
TestProcess();
}
private async void TestProcess()
{
proc_TestAll();
Task tsk_TestEnd = new Task(proc_TestEnd);
tsk_TestEnd.Start();
await tsk_TestEnd;
}
private async void proc_TestAll()
{
Task task1 = new(() => { Thread.Sleep(3000); });
task1.Start();
await task1;
while(true)
{
break;
}
Console.WriteLine("proc_TestAll Fin");
}
private void proc_TestEnd()
{
Console.WriteLine("proc_TestEnd Fin");
}
I expect that pressing the button will finish task1 in proc_TestAll, then output it to the console (after 3 seconds of sleep), and then run the task running proc_TestEnd. However, the result is that TestEnd Task will run immediately before Sleep is finished. What is the problem?
Thanks for reading.

Waiting for an event to fire while using SemaphoreSlim

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

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)

Update ProgressBar UI object from Task Parallel Library

Basically i would like to update ProgressBar UI object on the FormMain (WindowsForm). I am using .NET 4.0
Here are the code in the Form1.Designer.cs
namespace ProgressBarApp
{
public partial class Form1 : Form
{
private System.Windows.Forms.ProgressBar curProgressBar;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
CustomProcess theProcess = new CustomProcess();
theProcess.Process();
}
}
}
Here is the definition of CustomProcess.cs
namespace ProgressBarApp
{
class CustomProcess
{
public void Process()
{
for (int i = 0; i < 10; i++)
{
Task ProcessATask = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // simulating a process
}
);
Task UpdateProgressBar = ProcessATask.ContinueWith((antecedent) =>
{
// how do i update the progress bar object at UI here ?
}
);
}
}
}
}
You can use SynchronizationContext to do this. To use it for a Task, you need to create a TaskScheduler, which you can do by calling TaskScheduler.FromCurrentSynchronizationContext:
Task UpdateProgressBar = ProcessATask.ContinueWith(antecedent =>
{
// you can update the progress bar object here
}, TaskScheduler.FromCurrentSynchronizationContext());
This will work only if you call Process() directly from the UI thread.
How about using System.Reactive.Linq:
[UPDATE]
using System.Reactive.Linq;
namespace WindowsFormsApplication6
{
public partial class Form1 : Form
{
//private System.Windows.Forms.ProgressBar curProgressBar;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
CustomProcess theProcess = new CustomProcess();
var x = Observable.FromEventPattern(theProcess, "TaskCompleted");
curProgressBar.Maximum = 4;
x.Subscribe((a) =>
{
curProgressBar.Value = ((CustomProcess)a.Sender).Counter;
});
theProcess.Process();
}
}
class CustomProcess
{
public int Counter { get; set; }
public event EventHandler TaskCompleted = OnTaskCompleted;
private static void OnTaskCompleted(object sender, EventArgs e)
{
((CustomProcess)sender).Counter++;
}
public void Process()
{
for (int i = 0; i <= 3; i++)
{
Task ProcessATask = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // simulating a process
}
);
var awaiter = ProcessATask.GetAwaiter();
awaiter.OnCompleted(() =>
{
TaskCompleted(this, null);
});
}
}
}
}

Categories

Resources