I have an app with a start button that calls a long running time function. In order
to add a Stop button I've added a thread for this function to avoid the UI freezes and be able to stop the processing in anytime.
The code without threading in average takes 12 minutes to complete the processing, but with threading in the way I have below
takes 4 times more. Below is shown the code for the start button where is called the function "LongRunningFunction" . The function
needs a string argument to work "LongRunningFunction(Somestring)".
I've tested with Task.Run and Task.Factory.StartNew but it happens the same with both methods.
Is there an alternative way to set a thread for my case that doesn't affect too much the performance?
public partial class Form1 : Form
{
CancellationTokenSource cts = new CancellationTokenSource(); // Create the token source.
public Form1()
{
InitializeComponent();
}
private void Start_Click(object sender, EventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
cts = new CancellationTokenSource();
Task.Run(()=> LongRunningFunction(Somestring, cts.Token), cts.Token);
//Task.Factory.StartNew(() => LongRunningFunction(Somestring, cts.Token), cts.Token, TaskCreationOptions.None, TaskScheduler.Default);
}
private void Stop_Click(object sender, EventArgs e)
{
if (cts != null)
{
cts.Cancel();
cts = null;
MessageBox.Show("Processing cancelled");
}
}
public void LongRunningFunction(string String, CancellationToken token)
{
//Long running processing
//...
MessageBox.Show("Processing finished");
}
}
Update:
The only what I changed is the way I declare the function and added an if statement inside the while loop
that is inside the function. Is like below:
The CancelationToken was added in order to be able to stop the processing when Stop button is pressed.
Without thread I declare the function like this:
public void LongRunningFunction(string String)
{
while (condition)
{
//My code within While loop
}
MessageBox.Show("Processing finished");
}
and with Thread I define the function like this:
public void LongRunningFunction(string String, CancellationToken token)
{
while (condition)
{
if (token.IsCancellationRequested)
{
break;
}
//My code within While loop
}
if (!token.IsCancellationRequested)
{
MessageBox.Show("Processing finished");
}
}
Update2:
Inside LongRunningFunction() is called another function that prints the lines. Is like below.
public void LongRunningFunction(string fileName, CancellationToken token)
{
StreamWriter writer = new StreamWriter(#outputfile, true, Encoding.UTF8, 4096);
using (BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open)))
{
List<byte> buffer = new List<byte>();
List<string> buffer1 = new List<string>();
SoapHexBinary hex = new SoapHexBinary();
while (chunk.Length > 0)
{
if (token.IsCancellationRequested) // ### For Cancel Thread ###
{
break;
} // ### For Cancel Thread ###
chunk = reader.ReadBytes(1024);
foreach (byte data in chunk)
{
if (somecondition)
{
buffer.Add(data);
}
else if (other condition)
{
buffer.Add(data);
PrintFunction(buffer, hex, outputfile, writer); // Print Line
}
else if (some other condition)
{
buffer.Add(data);
}
}
}
if (!token.IsCancellationRequested)
{
MessageBox.Show("Processing finished");
}
}
if (writer != null)
{
writer.Dispose();
writer.Close();
}
}
private void PrintFunction(List<byte> buffer, SoapHexBinary hex, string outputfile, StreamWriter writer)
{
if (buffer.Count > 0)
{
if (buffer.Count >= lowlimit)
{
hex.Value = buffer.ToArray();
string Register = hex.ToString();
Regex pattern1 = new Regex(#"some pattern");
if (pattern1.IsMatch(Register))
{
Match l1 = Regex.Match(Register, #"somepattern", RegexOptions.IgnoreCase | RegexOptions.Compiled);
writer.Write("{0}|{1}|{2}", Convert.ToInt32(l1.Groups[1].ToString(), 16), l1.Groups[2].Value, l1.Groups[3].Value);
Match l2 = Regex.Match(Register, #"otherpattern", RegexOptions.IgnoreCase | RegexOptions.Compiled);
if (l2.Success)
{
foreach (Match m in Regex.Matches(l2.Groups[2].ToString(), pattern2, RegexOptions.IgnoreCase | RegexOptions.Compiled))
{
//Some foreach code
}
foreach (Match x in Regex.Matches(var, #"pattern"))
{
//come code
}
writer.WriteLine("," + String.Join(",", var1));
}
else
{
writer.WriteLine();
}
}
}
}
buffer.Clear();
}
Update3:
Hi bebosh,
I still have doubts how to apply in my function, the way you define the delegate in your example function.
My function looks like this:
public void LongRunningFunction(string fileName)
{
using (BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open)))
{
// some code
}
}
It could be something like this or how?:
private void LongRunningFunction(string fileName)
{
MethodInvoker action = delegate
{
using (BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open)))
{
// some code
}
};
}
Bebosh's answer was good enough. To increase the performance further you can set the ThreadPriority of the "thread" by setting the ".Priority = ThreadPriority.AboveNormal" right after setting the "thread.IsBackground = true;".
Could you try this code:
bool Stop = false;
Thread thread;
private void StartButton_Click(object sender, EventArgs e)
{
string FileName = #"...\a.bin";
thread = new Thread(new ThreadStart(() => DoLongProcess(FileName)));
thread.IsBackground = true;
thread.Start();
}
private void StopButton_Click(object sender, EventArgs e)
{
Stop = true;
}
private void DoLongProcess(string file)
{
using (BinaryReader reader = new BinaryReader(File.Open(file, FileMode.Open)))
{
int pos = 0;
int length = (int)reader.BaseStream.Length;
while (pos < length)
{
if (Stop)
thread.Abort();
// using Invoke if you want cross UI objects
this.Invoke((MethodInvoker)delegate
{
label1.Text = pos.ToString();
});
pos += sizeof(int);
}
}
}
use interrupting the thread
Thread thread;
public MainWindow()
{
InitializeComponent();
}
private async void StartButtonClick(object sender, RoutedEventArgs e)
{
thread = new Thread(ExecuteLong);
var task = Task.Run(() =>
thread.Start());
await task;
}
private void ExecuteLong()
{
try
{
// long task
}
catch (ThreadInterruptedException e)
{
MessageBox.Show("cancelled!");
return;
}
MessageBox.Show("finished");
}
private void CancelButtonClick(object sender, RoutedEventArgs e)
{
this.thread.Interrupt();
}
Related
I'm creating a simple program that pings all the servers on our network and returns whether the ping requests were successful.
I'm trying to utilise background workers so that the user can press the ping button and the pings run in the background while they can do other things on the UI
DoWork runs fine, there's no loop to keep it there infinitely, and it reaches the line:
r = pinger.Send(s)
and then from my understanding it ends and so the RunWorkCompleted method should be called?
I'm relearning programming after a long abscense so if I missed something obvious I apologise
...
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
}
private void Ping_Btn_Click(object sender, EventArgs e)
{
count = Convert.ToInt32(pingSeconds_TxtBox.Text);
if (backgroundWorker1.IsBusy != true)
{
// Start operation
backgroundWorker1.RunWorkerAsync();
}
}
private static void OnTimedEvent(Object source, ElapsedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if(worker.CancellationPending == true)
{
e.Cancel = true;
}
else
{
for(int i = 0; i <= count; i++)
{
MessageBox.Show("something is happening");
// Create ping object
Ping pinger = new Ping();
PingReply r;
// IP to test ping
string s = "###";
try
{
r = pinger.Send(s);
}
catch (Exception b)
{
MessageBox.Show(b.ToString());
}
}
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Show me something");
if(e.Cancelled == true)
{
statusLbl1.Text = "Cancelled";
} else if(e.Error != null)
{
statusLbl1.Text = "Error: " + e.Error.Message;
} else
{
statusLbl1.Text = "YEEEEEEEET";
}
}
...
You need to attach your backgroundWorker1_RunWorkerCompleted event handler to the RunWorkerCompleted event. The C# compiler doesn't hook handlers to events based on naming conventions. You have to do it explicitly.
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
I strongly suggest you convert this code to use async await which is much better at representing the flow of code control, rather than using the old BackgroundWorker which is basically deprecated.
Note the following:
The main event handler should be async void but all other async functions should be async Task.
Use of SemaphoreSlim.WaitAsync(0) to check if we are busy.
Ping object needs a using or finally to dispose it, as does the CancellationTokenSource.
<= count looks like it should be < count because you begin at 0.
SemaphoreSlim sem = new SemaphoreSlim(1, 1);
CancellationToken token;
private async void Ping_Btn_Click(object sender, EventArgs e)
{
if (!await sem.WaitAsync(0))
return;
var tokenSource = new CancellationTokenSource();
try
{
var count = Convert.ToInt32(pingSeconds_TxtBox.Text);
await RunPingsAsync(count, tokenSource.Token);
statusLbl1.Text = "YEEEEEEEET";
}
catch (OperationCanceledException)
{
statusLbl1.Text = "Cancelled";
}
catch (Exception e)
{
statusLbl1.Text = "Error: " + e.Error.Message;
}
finally
{
sem.Release();
tokenSource.Dispose();
}
MessageBox.Show("Show me something");
}
private Task RunPingsAsync(int count, CancellationToken token)
{
for(int i = 0; i < count; i++)
{
token.ThrowIfCancellationRequested();
MessageBox.Show("something is happening");
// IP to test ping
string s = "###";
// Create ping object
using (Ping pinger = new Ping())
{
var r = await pinger.SendPingAsync(s);
}
}
}
If you want to keep an infinite loop, then you have to make a loop in your backgroundWorker1_DoWork Method. Something like this
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker) sender;
while (!worker.CancellationPending)
{
//Do your stuff here
for(int i = 0; i <= count; i++)
{
MessageBox.Show("something is happening");
// Create ping object
Ping pinger = new Ping();
PingReply r;
// IP to test ping
string s = "###";
try
{
r = pinger.Send(s);
}
catch (Exception b)
{
MessageBox.Show(b.ToString());
}
}
}
}
Also, it is not a good idea to display message boxes from your background thread, Log it in console or any file.
I'm trying to replace my ProgressBar to a Progress Dialog using Mahapps.
So I started writing this:
private void btnClick(object sender, RoutedEventArgs e)
{
ConfRelais();
}
public async void ConfRelais()
{
var controller = await this.ShowProgressAsync("hey", "hoy");
controller.Maximum = 128;
while (flag == 0)
{
string data = RelayBoard_Port.ReadTo("\r\n");
if (data == "ok") { controller.SetMessage("Done Process");
flag = 1; }
else { controller.SetProgress(Int32.Parse(data)); }
}
await controller.CloseAsync();
}
But the progress dialog only displays when it's over.. As I'm still a beginner in c# maybe I'm missing some importants points to setup that kind of function.
You should execute the loop on a background thread:
public async void ConfRelais()
{
var controller = await this.ShowProgressAsync("hey", "hoy");
controller.Maximum = 128;
await Task.Run(() =>
{
while (flag == 0)
{
string data = RelayBoard_Port.ReadTo("\r\n");
if (data == "ok")
{
controller.SetMessage("Done Process");
flag = 1;
}
else { controller.SetProgress(Int32.Parse(data)); }
}
});
await controller.CloseAsync();
}
A single thread cannot both update the UI and execute your loop simultaneously.
You also don't really need a flag. You could just break out of the loop when you receive "ok":
while (true)
{
string data = RelayBoard_Port.ReadTo("\r\n");
if (data == "ok")
{
controller.SetMessage("Done Process");
break;
}
else { controller.SetProgress(Int32.Parse(data)); }
}
Basically I have a WPF application Where the user Write A process Name ,
Then a new Thread start where it keeps scanning if the process is Opened yet or not , The Thread will be Alive untill the process is Found .. So i can Get the handle and write The Memory !
private void scanBtn_Click (object sender, RoutedEventArgs e)
{
Thread s = new Thread(( ) => {
this.Dispatcher.Invoke((Action)(( ) =>
{
scanner(pName.Text);
}));
});
try
{
if (pName.Text != string.Empty)
{
InfoTxt.Text = "[ WAITING FOR PROCESS TO OPEN ]";
s.Start();
pName.IsEnabled = false;
if (!s.IsAlive)
{
pName.IsEnabled = true;
InfoTxt.Text = "[ FOUND ]";
Process p = Process.GetProcessesByName(pName.Text)[0];
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private static void scanner ( string procName)
{
while (true)
{
Process s = SeekProcName(procName);
if (s != null) break;
}
}
private static Process SeekProcName(string pName)
{
Process[] procs = Process.GetProcesses().Where(p => p.MainWindowHandle != (IntPtr)0).ToArray();
Process f = null;
foreach (var item in procs)
{
if (item.ProcessName.ToLower() == pName.ToLower())
{
f = item;
break;
}
}
return f;
}
The s thread is trying to run a delegate on the UI thread, which means it's no different than calling SeekProcName(procName) in the event handler directly.
It would be better to use Task.Run and async/await to run the check in a background thread. When await returns, execution resumes in the UI thread which means the UI can be updated without requiring Invoke or BeginInvoke
private async void scanBtn_Click (object sender, RoutedEventArgs e)
{
InfoTxt.Text = "[ WAITING FOR PROCESS TO OPEN ]";
//Read the textbox contets *before* starting the task
var name=pName.Text;
var p=await Task.Run(()=>SeekProcName(name));
if (p!=null)
{
InfoTxt.Text = "[ FOUND ]";
}
}
This can be called in a loop, with a small delay between iterations, without blocking the UI thread :
private async void scanBtn_Click (object sender, RoutedEventArgs e)
{
while(someCondition)
{
InfoTxt.Text = "[ WAITING FOR PROCESS TO OPEN ]";
var name=pName.Text;
var p=await Task.Run(()=>SeekProcName(name));
if (p!=null)
{
InfoTxt.Text = "[ FOUND ]";
}
await Task.Delay(1000);
}
}
I want to call a function in a different thread than my GUI.
I used the below code to trigger the function:
private void button1_Click(object sender, EventArgs e)
{
tokensource = new CancellationTokenSource();
var token = tokensource.Token;
Task.Run(()=>foo() , token);
}
private void foo()
{
// Uses some resources
}
private void button2_Click(object sender, EventArgs e)
{
tokenSource.Cancel();
}
How can I safely close the occupied resources in foo() when the task is cancelled?
You need to pass the token to the function as well. The cancellation token passed to Task.Run won't abort an already running task, it will prevent a scheduled task from running.
Inside foo, you can check the token for cancellation and return, or throw an exception. You can use a using block to dispose resources safely. Eg:
private void foo(CancellationToken token)
{
using(var reader=new StreamReader(somePath)
{
string line;
// Read the line if no cancellation was requested
while (!token.IsCancellationRequested && (line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
This code reads a line only if cancellation wasn't requested and returns quietly otherwise
You can also throw an OperationCancelledException by calling CancellationToken.ThrowIfCancellationRequested
private void foo(CancellationToken token)
{
using(var reader=new StreamReader(somePath)
{
string line;
// Read the line if no cancellation was requested
while ((line = sr.ReadLine()) != null)
{
token.ThrowIfCancellationRequested();
Console.WriteLine(line);
}
}
}
This will throw an exception that will be raised in the calling code when the task's result is retrieved, eg when using await Task.Run(..) or Task.Run(..).Wait()
Your Method should handle the CancellationToken like that:
public static void Main(string[] args)
{
var tokenSource = new CancellationTokenSource();
Console.WriteLine("Press CTRL+C to cancel important work");
Console.CancelKeyPress += (sender, eventArgs) => {
eventArgs.Cancel = true;
tokenSource.Cancel();
};
var task = Task.Run(() => foo(tokenSource.Token));
task.Wait();
WaitFor(action: "exit");
}
private static void foo(CancellationToken token)
{
const int Times = 10;
for (var x = 0; x < Times && token.IsCancellationRequested == false; ++x) {
Console.WriteLine("Important work");
Task
.Delay(200)
.Wait();
}
Console.WriteLine($"Free resources: {token.IsCancellationRequested}");
}
public static void WaitFor(ConsoleKey consoleKey = ConsoleKey.Escape, string action = "continue")
{
Console.Write($"Press {consoleKey} to {action} ...");
var consoleKeyInfo = default(ConsoleKeyInfo);
do {
consoleKeyInfo = Console.ReadKey(true);
}
while (Equals(consoleKeyInfo.Key, consoleKey) == false);
Console.WriteLine();
}
BR
incureforce
The code you're spinning up in a task should be responsible of taking into account cancellation. And the cancellation token you pass into the "Task.Run" method will be used only to cancel non-started tasks.
I am using C# .net 4.0 VS 2010.
I got a code in a form that basically adds a Task on form load in order to run a UDP Listener (on infinite loop). Whenever the Listener gets something from UDP socket, i add a line and the message to the multiline-textbox (this.textBox4.Text).
However i get an exception saying "Cross-thread operation not valid: "Contol 'textBox4' accessed from a thread other than the thread it was created on."
I didn't want to end the loop just to pass the value. Is there a way to do this? Here are my codes:
//main form load menu
private void frm_Menu_Load(object sender, EventArgs e)
{
Task<int> Listening = DoWorkAsync(1, "OpenYourEars");
.... // more code here
}
//async function
public Task<int> DoWorkAsync(int milliseconds, string WhatToDo)
{
return Task.Factory.StartNew<int>(() =>
{
if (WhatToDo == "OpenYourEars")
goListening();
... // more codes here
return 1;
});
}
//Listening on UDP socket
public void goListening()
{
bool done = false;
UdpClient listener = new UdpClient(listenPort);
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, listenPort);
string received_data;
byte[] receive_byte_array;
try
{
while (!done)
{
receive_byte_array = listener.Receive(ref groupEP);
received_data = Encoding.ASCII.GetString(receive_byte_array, 0, receive_byte_array.Length);
// display on TextBox4
this.textBox4.Text = "a\r\nb";
this.textBox4.Text = received_data.ToString().Trim();
}
}
catch (Exception e)
{
//gives "Contol 'textBox4' accessed from a thread other than
//the thread it was created on." when receiving a message.
MessageBox.Show(e.ToString());
}
listener.Close();
}
Version 2 - After answers by #cremor and #George87
private void frm_Menu_Load(object sender, EventArgs e)
{
MyValue = "Menu,7";
Task<int> Listening = DoWorkAsync(1, "OpenYourEars");
.... // more code here
}
private Task<int> DoWorkAsync(int milliseconds, string WhatToDo)
{
return Task.Factory.StartNew<int>(() =>
{
if (WhatToDo == "OpenYourEars")
goListening();
.... // more codes here
return 1;
});
}
//Listening
private void goListening()
{
bool done = false;
UdpClient listener = new UdpClient(listenPort);
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, listenPort);
string received_data;
byte[] receive_byte_array;
try
{
while (!done)
{
receive_byte_array = listener.Receive(ref groupEP);
received_data = Encoding.ASCII.GetString(receive_byte_array, 0, receive_byte_array.Length);
string aa = received_data.ToString().Trim();
if ( aa != "")
{
SetText("a\r\nb");
SetText(received_data.ToString().Trim());
aa = "";
}
}
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
listener.Close();
}
private delegate void SetTextCallback(string text);
private void SetText(string text)
{
try
{
if (this.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.BeginInvoke(d, new object[] { text });
}
else
{
SetText(text);
}
this.textBox4.Text = text;
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
....
UI controls can only be changed by the thread they were created in. You need to check InvokeRequired (WinForms) or Dispatcher.CheckAccess() (WPF) and then call Invoke/BeginInvoke.
Normally when using c# and use multi threading you should use delegates to make things work and not violate Cross-thread politics.
In other words you are not allowed to use objects defined in one thread from other threads. To make this happen you should use delegates to force the owning thread to perform the task for the calling thread.
Instead of:
// display on TextBox4
this.textBox4.Text = "a\r\nb";
you could use this:
define this methods:
delegate void SetTextCallback(string text);
private void SetText(string text)
{
if (this.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
SetText(text);
}
this.textBox1.Text = text;
}
and call them from the tread like this
SetText("a\r\nb");
You can try changing your async function to use the current syncronisation context
return Task.Factory.StartNew<int>(() =>
{
if (WhatToDo == "OpenYourEars")
goListening();
return 1;
},
new CancellationToken(),
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());