ThreadStart with Parameter method - c#

I am trying to learn Threading in .Net.
Many of you must have seen this:
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(loop));
t.Start();
}
private void loop()
{
for (int i = 0; i < 100000; i++)
{
textBox1.Text = i.ToString();
}
}
It works fine, but what if my loop method has parameters in it, like:
private void loop(string str)
{
for (int i = 0; i < 100000; i++)
{
textBox1.Text = i + str;
}
}
Then how to call this method in my ThreadStart as ThreadStart accepts just the method name. Then how to call loop method in a different thread?

Thread t = new Thread(new ParameterizedThreadStart(loop));
t.Start("Hello world");
private void loop(object obj)
{
string str = (string)obj;
for (int i = 0; i < 100000; i++)
{
// Don't do this: you can't change a control from another thread. Danger Will Robinson!
textBox1.Text = i + str;
}
}
Note that the loop method must accept an object parameter, so you'll have to upcast the object to your type. If you don't want, you can use a closure and an anonymous method:
string str = "Hello world";
Thread t = new Thread(() => {
for (int i = 0; i < 100000; i++)
{
// Don't do this: you can't change a control from another thread. Danger Will Robinson!
textBox1.Text = i + str;
}
});
t.Start();
In this way the anonymous method will "close" around str and it will be similar as if you had passed the parameter. Similar because there are differences/problems on closing variables. In reality I would write something similar to:
string str = "Hello world";
{
string str2 = str;
Thread t = new Thread(() => {
for (int i = 0; i < 100000; i++)
{
// Don't do this: you can't change a control from another thread. Danger Will Robinson!
textBox1.Text = i + str2;
}
});
t.Start();
}
so that no one else can "touch" str2.
If you need I can find some answer on SO that explain this "problem"

You'd use ParameterizedThreadStart instead: http://msdn.microsoft.com/en-us/library/system.threading.parameterizedthreadstart.aspx
Thread t = new Thread(new ParameterizedThreadStart(loop));
t.Start("Foo");
// Note the use of Object here to match the delegate signature
private void loop(Object state)
{
var str = state as String;
for (int i = 0; i < 100000; i++)
{
// For what it is worth, this is illegal:
// textBox1.Text = i + str;
// You need to Invoke back to the UI thread to access a control's properties:
textBox1.Invoke(()=> { textBox1.Text = i + str; });
}
}

There is a ParameterizedThreadStart class that Delegates with a single parameter can be cast to when instantiating a Thread:
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ParameterizedThreadStart(loop));
t.Start(str);
}
private void loop(string str)
{
for (int i = 0; i < 100000; i++)
{
//the code you had is a no-no when you are multithreading;
//all UI updates must occur on the main thread
//textBox1.Text = i + str;
UpdateTextBoxText(textBox1, i+str);
}
}
private void UpdateTextBoxText(TextBox textBox, string text)
{
//the method will invoke itself on the main thread if it isn't already running there
if(InvokeRequired)
{
this.Invoke((MethodInvoker)(()=>UpdateTextBoxText(TextBox textBox, string text)));
return;
}
textBox.Text = text;
}
If you don't need very fine-grained control over when the thread starts and stops, you can leave it to the ThreadPool and use Delegate.BeginInvoke:
private void button1_Click(object sender, EventArgs e)
{
Action<string> method = loop;
method.BeginInvoke(str, null, null);
}
private void loop(string str)
{
for (int i = 0; i < 100000; i++)
{
//textBox1.Text = i + str;
UpdateTextBoxText(textBox1, i+str);
}
}
private void UpdateTextBoxText(TextBox textBox, string text)
{
//the method will invoke itself on the main thread if it isn't already running there
if(InvokeRequired)
{
this.Invoke((MethodInvoker)(()=>UpdateTextBoxText(textBox, text)));
return;
}
textBox.Text = text;
}

Like this:
new Thread(() => loop("MyString")).Start();
You don't even have to mess with ThreadStart/ParameterizedThreadStart.

Take a look at ParameterizedThreadStart, it allows you to pass parameters into your thread start function.

Related

Shortest way to invoke a method with parameters in C#

When I need to invoke some code in the specified thread, i am using something like this:
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
delegate void MethodToInvokeDelegate(string foo, int bar);
void MethodToInvoke(string foo, int bar)
{
DoSomeWork(foo);
DoMoreWork(bar);
}
void SomeMethod()
{
string S = "Some text";
int I = 1;
dispatcher.BeginInvoke(new MethodToInvokeDelegate(MethodToInvoke), new object[] {S, I});
}
This code works fine, but it's quite heavy. I'd like to make it without declaring MethodToInvoke and MethodToInvokeDelegate - using an anonymous method. But I can't figure out how to pass parameters to it.
I can't write this like:
dispatcher.BeginInvoke((Action)delegate() { DoSomeWork(S); DoMoreWork(I); });
I need to actually pass parameters to method.
Is it any way to write it short and simple?
Example:
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
int[] ArrayToFill = new int[3];
void SomeMethod()
{
for (int i = 0; i < 3; i++)
dispatcher.BeginInvoke( { ArrayToFill[i] = 10; } );
}
This code will not work: method will be called with i = 1, 2, 3 and will raise IndexOutOfRange exception. i will be incremented before method begins to execute. So we need to rewrite it like:
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
int[] ArrayToFill = new int[3];
delegate void MethodToInvokeDelegate(int i);
void MethodToInvoke(int i)
{
ArrayToFill[i] = 10;
}
void SomeMethod()
{
for (int i = 0; i < 3; i++)
dispatcher.BeginInvoke(new MethodToInvokeDelegate(MethodToInvoke), new object[] {i});
}
if you wish to avoid creating a delegate type for each call, make use of Action<>, Action<,>, etc
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
string S = "Some text";
int I = 1;
dispatcher.BeginInvoke(
(Action<string, int>)((foo, bar) =>
{
MessageBox.Show(bar.ToString(), foo);
//DoSomeWork(foo);
//DoMoreWork(bar);
}),
new object[] { S, I }
);
an example with ArrayToFill[j] = 10; action can be fixed very simple:
for (int i = 0; i < 3; i++)
{
int j = i;
// lambda captures int variable
// use a new variable j for each lambda to avoid exceptions
dispatcher.BeginInvoke(new Action(() =>
{
ArrayToFill[j] = 10;
}));
}
Try this:
string S = "Some text";
int I = 1;
dispatcher.BeginInvoke(new Action(() =>
{
DoSomeWork(S);
DoMoreWork(I);
}));
[EDIT]
In response to your modified question:
The issue you are seeing there is modified closure problem.
To fix it, you merely need to copy the argument before invoking the method:
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
int[] ArrayToFill = new int[3];
for (int i = 0; i < 3; i++)
{
int index = i;
dispatcher.BeginInvoke(new Action(() => { ArrayToFill[index] = 10; } ));
}
yourUIcontrol.BeginInvoke(new MethodInvoker(delegate {
//your code goes here
}));

Ping Completed Event Handler not working [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
When I execute this code in command line, it's working fine:
class Program
{
private static List<Ping> pingers = new List<Ping>();
private static List<string> value = new List<string>();
private static int instances = 0;
private static object #lock = new object();
private static int result = 0;
private static int timeOut = 2500;
private static int ttl = 7;
public static void Main()
{
string baseIP = "192.168.1.";
Console.WriteLine("Pinging destinations of D-class in {0}*", baseIP);
CreatePingers(254);
PingOptions po = new PingOptions(ttl, true);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] data = enc.GetBytes("");
SpinWait wait = new SpinWait();
int cnt =1;
Stopwatch watch = Stopwatch.StartNew();
foreach (Ping p in pingers)
{
lock (#lock)
{
instances += 1;
}
p.SendAsync(string.Concat(baseIP, cnt.ToString()), timeOut, data, po);
cnt += 1;
}
//while (instances > 0)
//{
// wait.SpinOnce();
//}
watch.Stop();
for (int i = 0; i < value.Count; i++)
{
Console.WriteLine(value[i]);
}
DestroyPingers();
Console.WriteLine("Finished in {0}. Found {1} active IP-addresses.", watch.Elapsed.ToString(), result);
Console.ReadKey();
}
public static void Ping_completed(object s, PingCompletedEventArgs e)
{
lock (#lock)
{
instances -= 1;
}
if (e.Reply.Status == IPStatus.Success)
{
string sa = string.Concat("Active IP: ", e.Reply.Address.ToString());
value.Add(sa);
//Console.WriteLine(sa);
String diachiip = e.Reply.Address.ToString();
result += 1;
}
else
{
//Console.WriteLine(String.Concat("Non-active IP: ", e.Reply.Address.ToString()))
}
}
private static void CreatePingers(int cnt)
{
for (int i = 1; i <= cnt; i++)
{
Ping p = new Ping();
p.PingCompleted += Ping_completed;
pingers.Add(p);
}
}
private static void DestroyPingers()
{
foreach (Ping p in pingers)
{
p.PingCompleted -= Ping_completed;
p.Dispose();
}
pingers.Clear();
}
}
But when I convert from it to window form, it doesn't work. I don't kwow why, I have tried many different ways...
Code is here:
public partial class Form1 : Form
{
public static List<Ping> pingers = new List<Ping>();
public static List<string> value = new List<string>();
public static int instances = 0;
public static object #lock = new object();
public static int result = 0;
public int timeout = 2500;
public static int ttl = 7;
public Form1()
{
InitializeComponent();
}
public void btnscan_Click(object sender, EventArgs e)
{
string baseIP = "192.168.1.";
//int kt = Int32.Parse(txtkt.Text);
//int start = Int32.Parse(txtstart.Text);
CreatePingers(254);
PingOptions po = new PingOptions(ttl, true);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] data = enc.GetBytes("");
int cnt = 1;
Stopwatch watch = Stopwatch.StartNew();
foreach (Ping p in pingers)
{
lock (#lock)
{
instances += 1;
}
p.SendAsync(string.Concat(baseIP, cnt.ToString()), timeout, data, po);
cnt += 1;
}
watch.Stop();
//Result alway return 0
lst1.Items.Add(result.ToString());
lst1.Items.Add(value.Count.ToString());
for (int i = 0; i < value.Count; i++)
{
lst1.Items.Add(value[i]);
lst1.Items.Add("\n");
}
DestroyPingers();
string a = "Finished in " + watch.Elapsed.ToString() + ". Found " + result + " active IP-addresses.";
lst1.Items.Add(a);
}
public static void CreatePingers(int kt)
{
for (int start = 1; start <= kt; start++)
{
// class System.Net.NetworkInformation.Ping
Ping p = new Ping();
p.PingCompleted += Ping_completed();
pingers.Add(p);
}
}
public static PingCompletedEventHandler Ping_completed()
{
PingCompletedEventHandler a = new PingCompletedEventHandler(abc);
return a;
}
static void abc(object s, PingCompletedEventArgs e)
{
value.Add("abc");
lock (#lock)
{
instances -= 1;
}
if (e.Reply.Status == IPStatus.Success)
{
string abcd = string.Concat("Active IP: ", e.Reply.Address.ToString());
value.Add(abcd);
result += 1;
}
}
public static void DestroyPingers()
{
foreach (Ping p in pingers)
{
p.PingCompleted -= Ping_completed();
p.Dispose();
}
pingers.Clear();
}
}
What is wrong in this code?
Method SendAsync returns 0 because you are not waiting for it to complete. You are missing await and async (see msdn):
async void btnscan_Click(object sender, EventArgs e)
{
...
await p.SendAsync(string.Concat(baseIP, cnt.ToString()), timeout, data,
...
}
SpinWait was making code to work in console application. In winforms you should not use SpinWait (nor Sleep) in UI thread. You can create another thread (e.g. by using Task) and then you can copy/paste code from console application 1-to-1. But then you will need to use Invoke each time when you want to access UI controls.
async/await is really better.. if it will work (I concluded that from method name, I've no idea what method does, nor how to use it).
Perhaps I miss one thing, if SendAsync returns value, then you can get it by (the requirement to mark method where you use await with async still):
var result = await p.SendAsync(...);

C# winforms GUI will not accept data from other threads

Below is a function I have running in a while(true) loop in a thread running a Winforms GUI.
I have a button set to put text data into the inBuffer object. this always works, however when I place into the buffer object from a different thread, the data is detected, pulled, and printed out in the Console.out.WriteLine statement, however it never shows up in the Display (textBox) object
public void put()
{
string strDisplayMe = ModemKind.MainClass.inBuffer.MkRequest();
if (strDisplayMe != "")
{
Console.Out.WriteLine("FOUND SOMETHING IN BUFFER: " + strDisplayMe);
char[] DisplayMeArr = strDisplayMe.ToCharArray ();
for (int i = 0; i <= DisplayMeArr.Length -1; ++i)
{
this.display.Text += DisplayMeArr [i];
Thread.Sleep (100);
}
this.display.Text += '\n';
}
}
EDIT: this is a separate class from what is feeding it data through the static buffer objects
Only the main window thread can access/change controls... if you want update the UI from the thread that runs the loop, you need to sync the call with the main thread using the Control.Invoke method.
For instance...
public void put()
{
string strDisplayMe = ModemKind.MainClass.inBuffer.MkRequest();
if (strDisplayMe != string.Empty)
{
char[] DisplayMeArr = strDisplayMe.ToCharArray();
for (int i = 0; i <= DisplayMeArr.Length -1; ++i)
{
this.UpdateUI(() => { this.display.Text += DisplayMeArr[i]; });
Thread.Sleep(100);
}
this.UpdateUI(() => { this.display.Text += '\n'; });
}
}
private void UpdateUI(Action handler)
{
this.Invoke(handler);
}
You can use MethodInvoker to access TextBox from other thread then GUI thread. Make a method to access the TextBox using MethodInvoker. you are assigning text multiple times consider assigning it once for performance, and use StringBuilder for string concatenation.
public void put()
{
string strDisplayMe = ModemKind.MainClass.inBuffer.MkRequest();
if (strDisplayMe != "")
{
Console.Out.WriteLine("FOUND SOMETHING IN BUFFER: " + strDisplayMe);
char[] DisplayMeArr = strDisplayMe.ToCharArray ();
for (int i = 0; i <= DisplayMeArr.Length -1; ++i)
{
AssignToTextBox(this.display, this.display.Text + DisplayMeArr [i]);
Thread.Sleep (100);
}
AssignToTextBox(this.display, this.display.Text + '\n');
}
}
void AssignToTextBox(TextBox txtBox, string value)
{
if(txtBox.InvokeRequired)
{
txtBox.Invoke(new MethodInvoker(delegate { txtBox.text = value; }));
}
}
Edit You can use BeginInvoke instead of Invoke to call it asynchronously. Read more about the Invoke and BeginInvoke in this question.
void AssignToTextBox(TextBox txtBox, string value)
{
txtBox.BeginInvoke(new Action(()=>txtBox.Text = value ));
}

why do other events cannot be fired while "for loop" is in process?

I have a for loop and when the loop is being processed, I cant access any other function or event like clicking button it doesn't work till the for loop ends. Is there any way to overcome this Issue and hope I can get answer soon.
for (int i = 0; i < sizes - 2; i++)
{
if (pictureBox1.Image != null)
{
trackBar1.Value = trackBar1.Value + 1;
DisplayImage(_image);
}
}
Thanks in advance.
hi if you using framework 4.5
you can to the next :
Task.Run(() =>
{
for (int i = 0; i < sizes - 2; i++)
{
if (pictureBox1.Image != null)
{
trackBar1.Value = trackBar1.Value + 1;
DisplayImage(_image);
}
}
});
if not you can try this using thread :
Thread thread = new Thread(NewMethod);
thread.Start();
private void NewMethod()
{
for (int i = 0; i < sizes - 2; i++)
{
if (pictureBox1.Image != null)
{
trackBar1.Value = trackBar1.Value + 1;
DisplayImage(_image);
}
}
}
you can upgrade but you need to do it with delegate try this if you have cross thread operation error when update ui :
create delegate void function
delegate void Function();
then in your for make this :
Invoke(new Function(delegate()
{
label.text = "some text" ;
}));
This example shows how to create a new thread in .NET Framework. First, create a new ThreadStart delegate. The delegate points to a method that will be executed by the new thread. Pass this delegate as a parameter when creating a new Thread instance. Finally, call the Thread.Start method to run your method (in this case WorkThreadFunction) on background.
using System.Threading;
Thread thread = new Thread(new ThreadStart(WorkThreadFunction));
thread.Start();
The WorkThreadFunction could be defined as follows.
public void WorkThreadFunction()
{
try
{
// do any background work
}
catch (Exception ex)
{
// log errors
}
}

Display a string character by character on textbox

Is there any way i can display a String character by character? like in old RPGs
I've tried this:
string text1 ="this is a text";
for (int i = 0; i < text1.Length; i++)
{
textBox1.Text = "" + text1[i];
}
but it only replaces the last character on the text box.
Try This:
public partial class Form1 : Form
{
int count = 0;
string text1 = "this is a scrolling text";
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
textBox1.ReadOnly = true;
SetTimer(500);
}
private void SetTimer(int milliseconds)
{
timer1.Tick+=new EventHandler(timer1_Tick);
timer1.Interval = milliseconds;
timer1.Start();
}
private void timer1_Tick(Object o, EventArgs e)
{
if (count < text1.Length)
{
textBox1.Text += text1[count];
count++;
}
else
{
timer1.Stop();
button1.Enabled = true;
textBox1.ReadOnly = false;
}
}
}
Output:
The following code should do it:
public Form1()
{
InitializeComponent();
TypeText("this is a text");
}
private void TypeText(string text)
{
textBox1.Clear(); // Make sure the textbox is empty
Thread thread = new Thread(delegate() // Create a new thread which fills the textbox periodically
{
button1.BeginInvoke((MethodInvoker)delegate { button1.Enabled = false; }); // Disables the button
for (int i = 0; i < text.Length; i++)
{
int temp = i; // Cache variable because without this, an 'ArgumentOutOfRange' Exception will be thrown
textBox1.BeginInvoke((MethodInvoker)delegate // Invoke to main thread
{
textBox1.Text += text[temp]; // Fill with next char
});
if (text[temp] != ' ') // This makes sure the user doesn't have to wait the double of the time when there is an empty space for the new character
Thread.Sleep(500); // This will stop the seperate thread for 500ms. Won't block the main thread
}
button1.BeginInvoke((MethodInvoker)delegate { button1.Enabled = true; }); // Reenables the button
});
thread.Start(); // Start the new thread and continue the main thread
}
I'm not sure exactly how that helps you. You might want to add why you are doing this. The issue you are having with your code is that your are not appending to the string. Try this line instead:
textBox1.Text += text1[i];
You need to clear textBox1.Text before you start (set it to "").
A better approach might be to print out the hex values so that non printing characters are easily seen. You can do something like this:
string text1 ="this is a text";
var sb = new StringBuilder();
for (int i = 0; i < text1.Length; i++) {
sb.AppendFormat("{0:X} ", text1[i]);
}
textBox1.Text = sb.ToString();
Reading Jeroen Vannevel makes me thing that you are trying to create a "typewriter" affect. In that case try something like:
string text1 ="this is a text";
textBox1.Text = "";
for (int i = 0; i < text1.Length; i++) {
textBox1.Text += text1[i];
Thread.Sleep(250); // 1/4 sec delay
}
This code was written modeled on the code given. There is an implied expectation that this is run from the main UI thread. This means that when you are sitting in a loop with a Sleep delay your UI will be unresponsive (since you are tying up the main thread). You can overcome this by using the Invoke method on the Dispatcher object (of your App) and running the code on a different thread. You should only Invoke the parts that are owned by the main UI (textBox1) in this case.
textBox1.Text = textBox1.Text + text1[i]
To me it seems that you want to break a string into its characters, there are several ways to do that:
1 - String.GetChar() Method:
http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.strings.getchar%28v=vs.110%29.aspx
2 - String.Chars property:
http://msdn.microsoft.com/en-us/library/system.string.chars%28v=vs.110%29.aspx
TextBox1.Text += MyString.Chars[i];
Then if you want to show them one by one with a delay or something like that, you can use timers as others have suggested.

Categories

Resources