I have a problem when i wait for a task after i have canceled it with the CancellationTokenSource. The cancel call does not interrupt the task. When i wait
for the task the main thread blocks because the task will be never interrupted.
Here is a short description my program:
A task increments a char variable (from 'A' to 'Z') and shows it on the GUI thread. In order to do this the task executes a delegate (this.invoke()) on the thread the control was created on.
As soon as i comment out the RefreshTextBox()-Function the cancel call works and the task will be interrupted. It seems as if the this.invoke() command prevents the task from interrupting.
I the code below i have also implemented the same functionality with normal threads. And then i works. Where is the difference between task implementation and thread implementation?
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
public partial class frm_Main : Form
{
private delegate void dgt_StringHandler(string str_Value);
CancellationTokenSource _obj_Cts = null;
Thread _obj_Thread = null;
Task _obj_Task = null;
public frm_Main()
{
InitializeComponent();
}
private void CreateChar(ref char chr_Value)
{
int int_Value;
int_Value = (int)chr_Value;
int_Value++;
if (int_Value > 90 || int_Value < 65)
int_Value = 65;
chr_Value = (char)int_Value;
}
private void TestThread()
{
char chr_Value = '#';
bool bol_Stop = false;
while (!bol_Stop)
{
try
{
Thread.Sleep(300);
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
catch (ThreadInterruptedException)
{
bol_Stop = true;
}
}
}
private void TestTask(object obj_TokenTmp)
{
char chr_Value = '#';
CancellationToken obj_Token = (CancellationToken)obj_TokenTmp;
while (!obj_Token.IsCancellationRequested)
{
Thread.Sleep(300);
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
}
private void RefreshTextBox(string str_Value)
{
if (txt_Value.InvokeRequired)
{
dgt_StringHandler obj_StringHandler = new dgt_StringHandler(RefreshTextBox);
this.Invoke(obj_StringHandler, new object[] { str_Value });
}
else
{
txt_Value.Text = str_Value;
}
}
private void btn_StartStop_Click(object sender, EventArgs e)
{
if (_obj_Task == null && _obj_Thread == null)
{
if (opt_Task.Checked)
{
_obj_Cts = new CancellationTokenSource();
_obj_Task = new Task(new Action<object>(TestTask), _obj_Cts.Token, _obj_Cts.Token);
_obj_Task.Start();
}
else
{
_obj_Thread = new Thread(new ThreadStart(TestThread));
_obj_Thread.Start();
}
btn_StartStop.Text = "Stop";
}
else
{
if (_obj_Thread != null)
{
_obj_Thread.Interrupt();
_obj_Thread.Join();
_obj_Thread = null;
}
if (_obj_Task != null)
{
_obj_Cts.Cancel();
_obj_Task.Wait();
_obj_Task = null;
_obj_Cts = null;
}
btn_StartStop.Text = "Start";
}
}
}
These 2 pieces of the code together form a deadlock:
_obj_Cts.Cancel();
_obj_Task.Wait();
and
this.Invoke(obj_StringHandler, new object[] { str_Value });
You are calling Wait() on the main thread, and Invoke() needs to be handled by the main thread.
You can break the deadlock by using this.BeginInvoke(...) instead.
The Thread version uses Interrupt, a sledgehammer. So the thread won't try to call RefreshTextBox() after the stop signal.
Here is the adapted code. I now call BeginInvoke() instead of Invoke() as Henk Holterman has proposed. This works very fine and is the only right way to prevent a deadlock. There is also another case that must be considered. I have also a IAsyncResult object that is given through the BeginInvoke() call. This object I use to check whether the async call has already completed or not. If I wouldn't check it it could be that the GUI thread is not fast enough (for example a sleep statement somewhere in the GUI thread) to execute my delegate and cause of this my TestTask() method would always call BeginInvoke() although the GUI thread has not already completed the last delegate. The result would be that my GUI thread would block the application.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
namespace InvokeTest
{
public partial class frm_Main : Form
{
private delegate void dgt_StringHandler(string str_Value);
CancellationTokenSource _obj_Cts = null;
Thread _obj_Thread = null;
Task _obj_Task = null;
IAsyncResult _obj_Ar = null;
public frm_Main()
{
InitializeComponent();
}
private void CreateChar(ref char chr_Value)
{
int int_Value;
int_Value = (int)chr_Value;
int_Value++;
if (int_Value > 90 || int_Value < 65)
int_Value = 65;
chr_Value = (char)int_Value;
}
private void TestThread()
{
char chr_Value = '#';
bool bol_Stop = false;
while (!bol_Stop)
{
try
{
Thread.Sleep(1); // is needed for interrupting the thread
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
catch (ThreadInterruptedException)
{
bol_Stop = true;
}
}
}
private void TestTask(object obj_TokenTmp)
{
char chr_Value = '#';
CancellationToken obj_Token = (CancellationToken)obj_TokenTmp;
while (!obj_Token.IsCancellationRequested)
{
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
}
private void RefreshTextBox(string str_Value)
{
if (txt_Value.InvokeRequired)
{
if (_obj_Ar == null ||
_obj_Ar.IsCompleted)
{
dgt_StringHandler obj_StringHandler = new dgt_StringHandler(RefreshTextBox);
_obj_Ar = this.BeginInvoke(obj_StringHandler, new object[] { str_Value });
}
}
else
{
Thread.Sleep(200);
txt_Value.Text = str_Value;
}
}
private void btn_StartStop_Click(object sender, EventArgs e)
{
if (_obj_Task == null && _obj_Thread == null)
{
if (opt_Task.Checked)
{
_obj_Cts = new CancellationTokenSource();
_obj_Task = new Task(new Action<object>(TestTask), _obj_Cts.Token, _obj_Cts.Token);
_obj_Task.Start();
}
else
{
_obj_Thread = new Thread(new ThreadStart(TestThread));
_obj_Thread.Start();
}
btn_StartStop.Text = "Stop";
}
else
{
if (_obj_Thread != null)
{
_obj_Thread.Interrupt();
_obj_Thread.Join();
_obj_Thread = null;
}
if (_obj_Task != null)
{
_obj_Cts.Cancel();
_obj_Task.Wait();
_obj_Task = null;
_obj_Cts = null;
}
btn_StartStop.Text = "Start";
}
}
private void frm_Main_FormClosing(object sender, FormClosingEventArgs e)
{
if (_obj_Thread != null)
{
_obj_Thread.Interrupt();
_obj_Thread.Join();
}
if (_obj_Task != null)
{
_obj_Cts.Cancel();
_obj_Task.Wait();
}
}
}
}
Related
This question already has answers here:
From Eric Lippert's blog: "don't close over the loop variable" [duplicate]
(4 answers)
Captured variable in a loop in C#
(10 answers)
Closed 1 year ago.
This post was edited and submitted for review 1 year ago and failed to reopen the post:
Original close reason(s) were not resolved
I've made a real simple console app that I am using to break down a problem I am having on a larger application. I feel like I must have missed something in thread 101 class but I'm at a loss of what it is. Basically, the whole premise is to have a BackgroundWorker check on a timer a collection and then if another collection doesn't contain something from the first collection, start a new Task. If any anytime the Task isn't in running or created status, I want to cancel the task and create a new one.
The two behaviors I am noticing is one when there is an exception inside of the task, I am getting errors for Car Models that don't have an error (even though the task is new), and if I use a for loop my i iteration goes to 5 and the collection never has more than 4. I've also noticed that all 4 make an error at the same time, but I know there are some weird things with Random but I'm just not sure if that's the issue at this point.
If you want to see the for iteration go to 5, just use Y for the console readline, but this only happens in debug, running the application this doesn't seem to occur. The next thing I want to make sure of is that I am canceling the task properly.
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.ComponentModel;
using System.IO;
namespace ThreadTest
{
class Program
{
private static BackgroundWorker _backgroundWorker;
private static void _EstablishBackgroundWorker()
{
_backgroundWorker = new BackgroundWorker();
if (UseFor)
_backgroundWorker.DoWork += _UpdateCarsFor;
else
_backgroundWorker.DoWork += _UpdateCars;
Timer timer = new Timer(10000);
timer.Elapsed += _Timer_Elapsed;
timer.Start();
}
private static void _Timer_Elapsed(object sender, ElapsedEventArgs e)
{
while (_backgroundWorker.IsBusy)
{
}
if (!_backgroundWorker.IsBusy)
_backgroundWorker.RunWorkerAsync();
}
private static void _UpdateCarsFor(object sender, DoWorkEventArgs e)
{
string[] myCars = File.ReadAllLines(#"C:\cars\mycarfile.txt");
for (int i = 0; i < myCars.Length; i++)
if (!_cars.Where(x => x.Model == myCars[i].ToUpper()).Any())
_cars.Add(new Model.Car() { Model = myCars[i].ToUpper() });
for (int i = 0; i < _cars.Count; i++)
{
if (_cars[i].Task != null && _cars[i].Task.Status != TaskStatus.Running && _cars[i].Task.Status != TaskStatus.Created)
{
Console.WriteLine($"Making new task for { _cars[i].Model }");
_cars[i].tokenSource.Cancel();
_cars[i].RefreshToken();
_cars[i].Task = null;
}
if (_cars[i].Task == null)
{
_cars[i].Task = new Task(() => new Manufacture.Build().MakeCar(_cars[i].Model), _cars[i].cancellationToken);
_cars[i].Task.Start();
}
}
}
private static void _UpdateCars(object sender, DoWorkEventArgs e)
{
//string[] myCars = File.ReadAllLines(#"C:\cars\mycarfile.txt");
string[] myCars = new string[] { "F150", "RAM", "M3", "NSX" };
for (int i = 0; i < myCars.Length; i++)
if (!_cars.Where(x => x.Model == myCars[i].ToUpper()).Any())
_cars.Add(new Model.Car() { Model = myCars[i].ToUpper() });
foreach (var car in _cars)
{
if (car.Task != null && car.Task.Status != TaskStatus.Running && car.Task.Status != TaskStatus.Created)
{
Console.WriteLine($"Making new task for { car.Model }");
car.tokenSource.Cancel();
car.RefreshToken();
car.Task = null;
}
if (car.Task == null)
{
car.Task = new Task(() => new Manufacture.Build().MakeCar(car.Model), car.cancellationToken);
car.Task.Start();
}
}
}
private static List<Model.Car> _cars { get; set; }
private static bool UseFor { get; set; }
static void Main(string[] args)
{
_cars = new List<Model.Car>();
Console.WriteLine("Use for iteration? y/n");
var result = Console.ReadLine();
if (result.ToUpper() == "Y")
UseFor = true;
_EstablishBackgroundWorker();
Console.ReadLine();
}
}
}
Car.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadTest.Model
{
class Car
{
public Car()
{
RefreshToken();
}
public string Model { get; set; }
public Task Task { get; set; }
public CancellationToken cancellationToken { get; set; }
public CancellationTokenSource tokenSource { get; set; }
public void RefreshToken()
{
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
}
}
}
Build.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadTest.Manufacture
{
class Build
{
public void MakeCar(string car)
{
try
{
while (true)
{
Random r = new Random();
int chaos = r.Next(0, 100);
Console.WriteLine($"Building {car}");
Thread.Sleep(2000);
if (chaos >= 90) throw new Exception($"Something went wrong with {car}");
}
}
catch(Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
throw;
}
}
}
}
Here is an example of all threads erroring at the same time, but even if i turn random down to a 1% chance this still happens.
All errors
In the comments with Henk, the mention of closing over the loop variable I don't believe applies because this method present a foreach and the issue of all threads erroring at the same time occurs, and the question of property canceling a task still remains.
private static void _UpdateCars(object sender, DoWorkEventArgs e)
{
//string[] myCars = File.ReadAllLines(#"C:\cars\mycarfile.txt");
string[] myCars = new string[] { "F150", "RAM", "M3", "NSX" };
for (int i = 0; i < myCars.Length; i++)
if (!_cars.Where(x => x.Model == myCars[i].ToUpper()).Any())
_cars.Add(new Model.Car() { Model = myCars[i].ToUpper() });
foreach (var car in _cars)
{
if (car.Task != null && car.Task.Status != TaskStatus.Running && car.Task.Status != TaskStatus.Created)
{
Console.WriteLine($"Making new task for { car.Model }");
car.tokenSource.Cancel();
car.RefreshToken();
car.Task = null;
}
if (car.Task == null)
{
car.Task = new Task(() => new Manufacture.Build().MakeCar(car.Model), car.cancellationToken);
car.Task.Start();
}
}
}
I have a project that without usign any form/button or nothing like that, connects with a Websocket and using async methods receives some message(on a form created by myself) that is supposed to appear on the top-right corner of the screen.
But this message can appear from time to time (2 or 3 minutes) on the screen if the websocket doesn't say that it must stop. And this message can be big enough, that in order to make it look better I make my message appear in more than one form.
It causes an impression that it's a notification. So my class that connects with the websocket and receives the message async, calls another class using a thread that is a controller. The purpose of the controller is from time to time, show that message in various new form() notifications and obviously don't do it if the websocket doesn't return any message.
But when i call the form.show the program stops working.
I've looked around stackoverflow already, but the ideas that i've found didn't seem to work.
Some say that I should use invoke, but it kept giving error saying that
"Invoke or BeginInvoke cannot be called on a control until the window handle has been created", tried to solve like this: C# calling form.show() from another thread but it didn't work.
Some said that I should use .showDialog instead of .show, but it doesn't appear to be good, because it waits the window to be closed to terminate the method and as I said I need to open more than one notification at the same time.
Some said that the form was open with .show, but it was open for a very little period of time. But i couldn't notice if that was the case and even if it was i couldn't solve it. Well, what matter is that i'm stuck and i don't know what to do more.
Edited with Code:
//Main
Application.Run(new SocketService());
//SocketService class
public SocketService()
{
alerta = null;
while (true)
{
try
{
//Console.WriteLine("Nome do UsĂșario:" + Environment.UserName);
Thread.Sleep(2000);
Connect("ws://192.168.120.38:9091").Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
public static async Task Connect(string uri)
{
ClientWebSocket webSocket = null;
try
{
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
await Login(webSocket);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (webSocket != null)
webSocket.Dispose();
lock (consoleLock)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("WebSocket closed.");
Console.ResetColor();
}
}
}
private static async Task Login(ClientWebSocket webSocket)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(encoder.GetBytes( "{\"event\":\"loginBrowser\",\"data\":{\"login\":\"000000003077\",\"data\":\"1\"}}"));
await webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
if (webSocket.State == WebSocketState.Open)
{
if (ShowMessage.created != true)
{
var dummy = new Control(); // to initialize SynchronizationContext
_sync = SynchronizationContext.Current;
new Thread(ThreadProc).Start();
}
await Receive(webSocket);
}
}
private static async Task Receive(ClientWebSocket webSocket)
{
while (webSocket.State == WebSocketState.Open)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[256]);
var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
else
{
if (result.EndOfMessage)
{
message += encoder.GetString(buffer.ToArray());
SendMessage(message);
}
else
{
message += encoder.GetString(buffer.ToArray());
}
}
}
}
public static void ShowFormFromAnotherThread(string text)
{
_sync.Post(SendOrPostCallback, text);
}
private static void SendOrPostCallback(object state)
{
var form = new Notification();
form.Text = (string)state;
form.Show();
}
private static void ThreadProc()
{
while (true)
{
Thread.Sleep(2000); // wait imitation
ShowFormFromAnotherThread("HI");
}
}
/*Notification is my form and depending on where I put this part:
var dummy = new Control(); // to initialize SynchronizationContext
_sync = SynchronizationContext.Current;
new Thread(ThreadProc).Start();
Or i doesn't call login or doesn't enter receive() method or the best case It receives the information
calls the threadProc and the ShowFormFromAnotherThread but doesn't enter SednOrPostCallBack*/
using System.Threading;
using System.Windows.Forms;
namespace ConsoleThreadSync
{
internal class Program
{
private static void Main(string[] args)
{
Application.Run(new App());
}
}
public class App : ApplicationContext
{
private readonly SynchronizationContext _sync;
public App()
{
var dummy = new Control(); // to initialize SynchronizationContext
_sync = SynchronizationContext.Current;
new Thread(ThreadProc).Start();
}
public void ShowFormFromAnotherThread(string text)
{
_sync.Post(SendOrPostCallback, text);
}
private void SendOrPostCallback(object state)
{
var form = new Form1();
form.Text = (string)state;
form.Show();
}
private void ThreadProc()
{
while (true)
{
Thread.Sleep(2000); // wait imitation
ShowFormFromAnotherThread("HI");
}
}
}
}
Try to call this:
var dummy = new Control(); // to initialize SynchronizationContext
_sync = SynchronizationContext.Current;
from a contructor SocketService() and not from async methods. This is an initialization code and it must call from main thread.
Okay, after reading a little bit more, the solution that kind worked out was this one, but the only way of using the
.show from the notifician is to use the Application.DoEvents and I've been warned from the sources that I've looked into
that this method should not be used, unless is the only option, because It can cause some problems with the Threads and other things.
So unless someone can give me another hint or clue about what to do, I have two options or use this method and try to fix some other bug
that It can cause or use the .showDialog because don't know why It works without any other problem, but to use .showDialog I've
to use another thread where I create and show the notification because if I don't do, the loop will stop at each iteration
in order to wait the .showDialog be closed. And as it isn't a problem I want to avoid using a lot of threads, because it can cause
another problem with the sync between them:
namespace ReiDoCSharp
{
class ShowMessage
{
private static RootObject alerta;
public static bool created;
private static int startPosition;
public static void setStartPosition(int start)
{
if (start < startPosition)
{
startPosition = start;
}
}
public RootObject getAlerta()
{
return ShowMessage.alerta;
}
public void setAlerta(RootObject root)
{
ShowMessage.alerta = root;
}
private static void DoWork()
{
while (true)
{
if (created != true)
{
created = true;
}
if (alerta != null)
{
string mensagem = "";
if ((alerta.data.Informacoes[1] != "") && (alerta.data.Informacoes[1] != null))
{
mensagem += alerta.data.Informacoes[1];
}
if ((alerta.data.Informacoes[0] != "") && (alerta.data.Informacoes[0] != null))
{
mensagem += alerta.data.Informacoes[0];
}
if (mensagem != "")
{
startPosition = 5;
string[] messages = mensagem.Split(new[] { "<br><br>" }, StringSplitOptions.None);
foreach (string message in messages)
{
Notification popup = new Notification();
popup.label1.Text = message;
popup.TopMost = true;
popup.Show();
Application.DoEvents();
/*Solution with the ShowDialog would be:
Task.Run(() => showNotification(message));
*/
}
}
}
Thread.Sleep(5000);
}
}
//Then I won't need to use Application.DoEvents, but would have to create more threads
private static Task showNotification(string message)
{
Notification popup = new Notification();
popup.label1.Text = message;
popup.TopMost = true;
popup.ShowDialog();
}
public static Task createPopupsAsync()
{
Task.Run(() => DoWork());
}
}
}
namespace ReiDoCSharp
{
class SocketService
{
private static object consoleLock = new object();
private const bool verbose = true;
private static readonly TimeSpan delay = TimeSpan.FromMilliseconds(3000);
private static UTF8Encoding encoder = new UTF8Encoding();
private static string message;
private static RootObject alerta;
public SocketService()
{
Begin();
}
public static void Begin()
{
alerta = null;
while (true)
{
try
{
Thread.Sleep(2000);
Connect("ws://192.168.120.38:9091").Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
public static async Task Connect(string uri)
{
ClientWebSocket webSocket = null;
try
{
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
await Login(webSocket);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (webSocket != null)
webSocket.Dispose();
lock (consoleLock)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("WebSocket closed.");
Console.ResetColor();
}
}
}
private static async Task Login(ClientWebSocket webSocket)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(encoder.GetBytes("{\"event\":\"loginBrowser\",\"data\":{\"OPERADOR\":\"000000003077\",\"NRORG\":\"1\"}}"));
await webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
if (webSocket.State == WebSocketState.Open)
{
Task.Factory.StartNew(() => ShowMessage.createPopupsAsync());
await Receive(webSocket);
}
}
private static async Task Receive(ClientWebSocket webSocket)
{
while (webSocket.State == WebSocketState.Open)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[256]);
var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
else
{
if (result.EndOfMessage)
{
message += encoder.GetString(buffer.ToArray());
SendMessage(message);
}
else
{
message += encoder.GetString(buffer.ToArray());
}
}
}
}
private static void LogStatus(bool receiving, byte[] buffer, int length, string assunto)
{
lock (consoleLock)
{
Console.ForegroundColor = receiving ? ConsoleColor.Green : ConsoleColor.Yellow;
if (verbose)
{
Console.WriteLine(encoder.GetString(buffer) + " " + assunto);
}
Console.ResetColor();
}
}
private static void SendMessage(string message)
{
message = message.Replace("event", "evento");
message = message.Replace("\0", "");
JavaScriptSerializer js = new JavaScriptSerializer();
RootObject mess = js.Deserialize<RootObject>(message);
if (mess.data.Informacoes[1] != "")
{
mess.data.Informacoes[1] += "<br>";
}
if (alerta == null)
{
alerta = mess;
}
else
{
if ((mess.data.Quantidade[0] != 0) && (mess.data.Quantidade == null))
{
if ((mess.data.Quantidade[0] == -1) && (mess.data.Informacoes[0] == ""))
{
alerta = null;
}
else
{
alerta = mess;
}
}
else if (mess.data.Quantidade[0] == 0)
{
alerta = null;
}
if ((mess.data.Quantidade[1] != 0) && (mess.data.Informacoes[1] != ""))
{
alerta = mess;
}
}
new ShowMessage().setAlerta(alerta);
message = "";
}
}
}
On the face of it it seems quite simple.
I have a switch case and if the condition is met I would like to print text to a label with a animation.
in this case a type writer animation.
I have already made the animation however I cant seem to integrate a similar version it into the switch case itself.
Any help?
Type Writer Animation code c#:
public partial class Form1 : Form
{
int _charIndex = 0;
string _text = "This is a test.";
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
_charIndex = 0;
label1.Text = string.Empty;
Thread t = new Thread(new ThreadStart(this.TypewriteText));
t.Start();
}
private void TypewriteText()
{
while (_charIndex < _text.Length)
{
Thread.Sleep(50);
label1.Invoke(new Action(() =>
{
label1.Text += _text[_charIndex];
}));
_charIndex++;
}
}
}
}
And the animation code needs to be placed into this:
Switch case code:
void TestEngine(object sender, SpeechRecognizedEventArgs e)
{
switch (e.Result.Text)
{
case "Test":
//Label animation code goes here
break;
Thanks in advance!
Short answer - move the code in a method and call it from anywhere you want.
Long answer
While it works, it doesn't make sense because all the worker thread does is sleeping and then calling the UI thread. System.Windows.Forms.Timer based approach would be much appropriate for this concrete case. The "modern" approach would be based on async/await. If you need flexibility, the last one is the best choice. But whatever you choose, you'll hit a reentrancy problem at some point and will need to handle it. The best would be to prepare some helper utility class and use it from anywhere. Here is an example:
using System;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Tests
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());
}
class TestForm : Form
{
public TestForm()
{
var label = new Label { Parent = this, AutoSize = true, Top = 8, Left = 8 };
animateHelper = new AnimateHelper(label);
int left = 8;
foreach (var action in (ButtonAction[])Enum.GetValues(typeof(ButtonAction)))
{
var button = new Button { Parent = this, AutoSize = true, Text = action.ToString(), Left = left };
button.Top = DisplayRectangle.Bottom - button.Height - 8;
button.Click += (sender, e) => Execute(action);
left += button.Width + 8;
}
}
protected override void Dispose(bool disposing)
{
if (disposing && animateHelper != null) animateHelper.Cancel();
base.Dispose(disposing);
}
enum ButtonAction { TypewriteText, RepeatText, Cancel }
private void Execute(ButtonAction action)
{
// the original question
switch (action)
{
case ButtonAction.TypewriteText:
TypewriteText("This is a typewriter text animantion test.");
break;
case ButtonAction.RepeatText:
RepeatText("This is a repeating text animantion test.");
break;
case ButtonAction.Cancel:
animateHelper.Cancel();
break;
}
}
AnimateHelper animateHelper;
void TypewriteText(string text)
{
animateHelper.Execute(async (output, ct) =>
{
bool clear = true;
try
{
if (string.IsNullOrEmpty(text)) return;
output.ForeColor = Color.Blue;
for (int length = 1; ; length++)
{
if (ct.IsCancellationRequested) return;
output.Text = text.Substring(0, length);
if (length == text.Length) break;
await Task.Delay(50, ct);
}
clear = false;
}
finally { if (clear) output.Text = string.Empty; }
});
}
void RepeatText(string text)
{
animateHelper.Execute(async (output, ct) =>
{
try
{
if (string.IsNullOrEmpty(text)) return;
output.ForeColor = Color.Red;
while (true)
{
for (int length = 1; length <= text.Length; length++)
{
if (ct.IsCancellationRequested) return;
output.Text = text.Substring(text.Length - length);
await Task.Delay(50, ct);
}
for (int pad = 1; pad < text.Length; pad++)
{
if (ct.IsCancellationRequested) return;
output.Text = new string(' ', pad) + text.Substring(0, text.Length - pad);
await Task.Delay(50, ct);
}
if (ct.IsCancellationRequested) return;
output.Text = string.Empty;
await Task.Delay(250, ct);
}
}
finally { output.Text = string.Empty; }
});
}
}
class AnimateHelper
{
Label output;
Task task;
CancellationTokenSource cts;
public AnimateHelper(Label output) { this.output = output; }
void Reset()
{
if (cts != null) { cts.Dispose(); cts = null; }
task = null;
}
public void Cancel() { DontCare(CancelAsync()); }
async Task CancelAsync()
{
if (task != null && !task.IsCompleted)
{
try { cts.Cancel(); } catch { }
try { await task; } catch { }
}
Reset();
}
public void Execute(Func<Label, CancellationToken, Task> action) { DontCare(ExecuteAsync(action)); }
async Task ExecuteAsync(Func<Label, CancellationToken, Task> action)
{
await CancelAsync();
cts = new CancellationTokenSource();
task = action(output, cts.Token);
try { await task; } catch { }
Reset();
}
// make compiler happy
static void DontCare(Task t) { }
}
}
}
Question 1: I want to refresh a label by a work thread by delegate and invoke. It works well until i try to close the form. In closing event, I stop the work thread and then the UI thread while an exception occurs(Object disposed exception). It seems that form1 is disposed. I don't know what's wrong.
Question 2: When running this code, the memory usage keep increasing. I don't think it should take so much memory space. You can see find this by checking the task manager.
here's my code:
(.Net Framework 4, winforms)
Scheduler:
class Scheduler
{
private Thread[] workThreads = null;
//Scheduler started flag
public static bool bSchedulerStarted = false;
//Work threads stop event
public static EventWaitHandle stopWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
private static Scheduler self = null;
public static Scheduler getInstance()
{
if (self == null)
{
self = new Scheduler();
}
return self;
}
private Scheduler()
{
workThreads = new Thread[1];
}
private void CreateThread()
{
workThreads[0] = new Thread(Worker.doWork);
workThreads[0].IsBackground = true;
}
public void startUp()
{
if (!bSchedulerStarted)
{
stopWaitHandle.Reset();
CreateThread();
//Start all work threads
for (int i = 0; i < 1; i++)
{
workThreads[i].Start();
}
bSchedulerStarted = true;
}
}
public void stop()
{
if (!bSchedulerStarted)
return;
//Send stop event
stopWaitHandle.Set();
bSchedulerStarted = false;
if (workThreads != null)
{
//wait for all work threads to stop
for (int i = 0; i <1; i++)
{
if (workThreads[i] != null && workThreads[i].IsAlive)
workThreads[i].Join();
}
}
}
}
Worker:
class Worker
{
public static void doWork()
{
while (true)
{
if (Scheduler.stopWaitHandle.WaitOne(10, false) == true)
{
break;
}
Form1.count++;
Form1.sysMsgEvent.Set();
Thread.Sleep(10);
}
}
}
And form:
public partial class Form1 : Form
{
public static int count = 0;
public static EventWaitHandle sysMsgEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
public static EventWaitHandle stopEvent = new EventWaitHandle(false, EventResetMode.ManualReset);
private EventWaitHandle[] waitEvent = null;
Thread UIThread = null;
private delegate void ShowMsg();
public Form1()
{
InitializeComponent();
waitEvent = new EventWaitHandle[2];
waitEvent[0] = stopEvent;
waitEvent[1] = sysMsgEvent;
}
public void UpdateUI()
{
while (true)
{
switch (EventWaitHandle.WaitAny(waitEvent))
{
case 0: //Stop UI thread
return;
case 1: //Refresh UI elements
updateLabel();
break;
default:
return;
}//switch
}//while
}
private void updateLabel()
{
if (label1.InvokeRequired)
{
ShowMsg d = new ShowMsg(updateLabel);
this.Invoke(d, null);
}
else
{
label1.Text = count.ToString();
}
}
private void Form1_Load(object sender, EventArgs e)
{
UIThread = new Thread(new ThreadStart(UpdateUI));
UIThread.Start();
Scheduler sc = Scheduler.getInstance();
sc.startUp();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Scheduler sc = Scheduler.getInstance();
sc.stop();
//stop UI thread
Form1.stopEvent.Set();
}
}
I want to know when all my async threads have completed so I know when to close my loading form. My code never closes the loading form. I don't know why. I'm unsure how to correctly pass my ManualResetEvent object to the async thread too.
I'm also open to a simpler means to achieve my goal of knowing when to close the loading form.
UPDATE
After reading the advice here I've updated my class. Unfortunetly, it still does not work. I feel closer though. It's just that the callback never fires.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Threading;
namespace BrianTests
{
public class TaskInfo
{
public RegisteredWaitHandle Handle;
public string OtherInfo = "default";
public Form loading;
}
public partial class AsyncControlCreateTest : Form
{
//List<ManualResetEvent> MREs = new List<ManualResetEvent>();
Form loading = new Form() { Text = "Loading...", Width = 100, Height = 100 };
CountdownWaitHandle cdwh;
public AsyncControlCreateTest()
{
InitializeComponent();
}
private void AsyncControlCreateTest_Load(object sender, EventArgs e)
{
loading.Show(this);//I want to close when all the async threads have completed
CreateControls();
}
private void CreateControls()
{
int startPoint= 0;
int threadCount = 2;
cdwh = new CountdownWaitHandle(threadCount);
for (int i = 0; i < threadCount; i++)
{
ManualResetEvent mre = new ManualResetEvent(initialState: true);
UserControl control = new UserControl() { Text = i.ToString() };
control.Load += new EventHandler(control_Load);
Controls.Add(control);
control.Top = startPoint;
startPoint += control.Height;
//MREs.Add(mre);
//mre.Set();//just set here for testing
}
Task.Factory.StartNew(new Action(() =>
{
TaskInfo info = new TaskInfo();
info.loading = loading;
try
{
info.Handle = ThreadPool.RegisterWaitForSingleObject(cdwh, WaitProc, info, 4000, executeOnlyOnce: false);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}));
}
public static void WaitProc(object state, bool timedOut)
{//this callback never occurs...
TaskInfo ti = (TaskInfo)state;
string cause = "TIMED OUT";
if (!timedOut)
{
cause = "SIGNALED";
// If the callback method executes because the WaitHandle is
// signaled, stop future execution of the callback method
// by unregistering the WaitHandle.
if (ti.Handle != null)
ti.Handle.Unregister(null);
}
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
ti.OtherInfo,
Thread.CurrentThread.GetHashCode().ToString(),
cause
);
ti.loading.Close();
}
void control_Load(object sender, EventArgs e)
{
RichTextBox newRichTextBox = new RichTextBox();
UserControl control = sender as UserControl;
control.Controls.Add(newRichTextBox);
Task.Factory.StartNew(new Action(() =>
{
Thread.Sleep(2000);
newRichTextBox.Invoke(new Action(() => newRichTextBox.Text = "loaded"));
cdwh.Signal();
}));
}
}
public class CountdownWaitHandle : WaitHandle
{
private int m_Count = 0;
private ManualResetEvent m_Event = new ManualResetEvent(false);
public CountdownWaitHandle(int initialCount)
{
m_Count = initialCount;
}
public void AddCount()
{
Interlocked.Increment(ref m_Count);
}
public void Signal()
{
if (Interlocked.Decrement(ref m_Count) == 0)
{
m_Event.Set();
}
}
public override bool WaitOne()
{
return m_Event.WaitOne();
}
}
}
The problem is that WaitHandle.WaitAll is throwing an exception, which you can see:
try
{
WaitHandle.WaitAll(MREs.ToArray());
}
catch (Exception e) {
MessageBox.Show(e.Message);
throw;
}
The error message is that "WaitAll for multiple handles on a STA thread is not supported."
If you do something like
foreach(var m in MREs)
m.WaitOne();
It will work.
I'm not quite sure why the exception did not crash the application, as I would have hoped. See perhaps How can I get WinForms to stop silently ignoring unhandled exceptions? for this.
Locking the MRE's and moving the WaitAll off the STA thread does the trick.
public partial class AsyncControlCreateTest : Form
{
object locker = new object();
static List<ManualResetEvent> MREs = new List<ManualResetEvent>();
Form loading = new Form() { Text = "Loading...", Width = 100, Height = 100 };
public AsyncControlCreateTest()
{
InitializeComponent();
}
private void AsyncControlCreateTest_Load(object sender, EventArgs e)
{
loading.Show(this);//I want to close when all the async threads have completed
CreateControls();
}
private void CreateControls()
{
int startPoint= 0;
for (int i = 0; i < 100; i++)
{
ManualResetEvent mre = new ManualResetEvent(initialState: false);
UserControl control = new UserControl() { Text = i.ToString() };
control.Load += new EventHandler(control_Load);
Controls.Add(control);
control.Top = startPoint;
startPoint += control.Height;
MREs.Add(mre);
}
Task.Factory.StartNew(new Action(() =>
{
try
{
WaitHandle.WaitAll(MREs.ToArray());
}
catch (Exception ex)
{
MessageBox.Show("error " + ex.Message);
}
finally
{
MessageBox.Show("MRE count = " + MREs.Count);//0 count provides confidence things are working...
loading.Invoke(new Action( () => loading.Close()));
}
}));
}
void control_Load(object sender, EventArgs e)
{
RichTextBox newRichTextBox = new RichTextBox();
UserControl control = sender as UserControl;
control.Controls.Add(newRichTextBox);
Task.Factory.StartNew(new Action(() =>
{
Thread.Sleep(500);
newRichTextBox.Invoke(new Action(() => newRichTextBox.Text = "loaded"));
lock (locker)
{
var ev = MREs.First();
MREs.Remove(ev);
ev.Set();
}
}));
}
}