I'm trying to update a listbox from another thread, what is the simplest way to achieve this?
I tried Invoking the textbox, but it didn't work.
private void dowork()
{
TcpClient client = new TcpClient();
client.Connect("192.168.1.3", 10);
StreamWriter writer = new StreamWriter(client.GetStream());
StreamReader reader = new StreamReader(client.GetStream());
JObject o = new JObject();
o.Add("comando", 1);
o.Add("dir", #"C:\Users\klein\Desktop\Acionamentos");
writer.Write(o.ToString());
writer.Flush();
JArray array = JArray.Parse(reader.ReadToEnd());
for (int i = 0; i < array.Count; i++)
{
listBox1.Items.Add(array[i]); //update GUI from this thread
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(dowork);
t.Start();
}
I would use a BackgroundWorker, you can do your async stuff in DoWork, report your progress using bgworker.ReportProgress() and you will get the callback in ProgressChanged (I would call ReportProgress for every element processed), then you will be able to update your GUI controls.
The worker can also fire RunWorkerCompleted when it ends.
YourType arrayitem;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
TcpClient client = new TcpClient();
client.Connect("192.168.1.3", 10);
StreamWriter writer = new StreamWriter(client.GetStream());
StreamReader reader = new StreamReader(client.GetStream());
JObject o = new JObject();
o.Add("comando", 1);
o.Add("dir", #"C:\Users\klein\Desktop\Acionamentos");
writer.Write(o.ToString());
writer.Flush();
JArray array = JArray.Parse(reader.ReadToEnd());
int percentage;
for (int i = 0; i < array.Count; i++)
{
arrayitem = array[i];
percentage = ((i + 1)*100)/array.Count;
backgroundWorker1.ReportProgress(percentage);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
listBox1.Items.Add(arrayitem);
progressBar1.Value = e.ProgressPercentage; // in case you'd want to add a progressbar
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label1.Text = "Done";
}
Keep in mind your backgroundworker must have true in the WorkerReportProgress property.
You can use Control.Invoke to get on UI thread and then update the list box.
However, I suggest you look at using a BackgroundWorker.
You need to use Dispatcher.BeginInvoke. You can check this link to have better understanding on how to update the UI controls from different thread.
For example, if the listbox is named listbox1
for (int i = 0; i < array.Count; i++)
{
listBox1.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(delegate() { listBox1.Items.Add(array[i]) });
}
Related
I'm having some trouble accessing the UI from an another thread.
I understand the basics on cross-threading limitations, but I can't seem to write the code that will work. More specifically, I can't access the ListView from a static method (thread).
I'm trying to make it work with backgroundWorker.
Here's my code:
private void start_Click(object sender, EventArgs e)
{
ServicePointManager.DefaultConnectionLimit = 20;
var tasks = new List<Task<int>>();
foreach (ListViewItem item in listView1.Items)
{
string currentUrl = item.SubItems[1].Text;
int i = item.Index;
tasks.Add(Task.Factory.StartNew(() => { return GetWebResponse(currentUrl, i); }));
}
Task.WaitAll(tasks.ToArray());
}
private static int GetWebResponse(string url, int itemIndex)
{
int statusCode = 0;
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
Task<HttpWebResponse> responseTask = Task.Factory.FromAsync<HttpWebResponse>(httpWebRequest.BeginGetResponse, asyncResult => (HttpWebResponse)httpWebRequest.EndGetResponse(asyncResult), null);
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
backgroundWorker.RunWorkerAsync();
return statusCode;
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
listView1.Items[0].ImageKey = "green";
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!e.Cancel)
{
Thread.Sleep(5000);
backgroundWorker.ReportProgress(0);
}
}
This code doesn't work because backgroundWorker_DoWork and backgroundWorker_ProgressChanged are not static, but if I make them static then I can't access listView1
EDIT: Got it working. Code below for review
public delegate void delUpdateListView(int itemIndex, int statusCode);
public Form1()
{
InitializeComponent();
}
private void start_Click(object sender, EventArgs e)
{
ServicePointManager.DefaultConnectionLimit = 20;
var tasks = new List<Task<int>>();
foreach (ListViewItem item in listView1.Items)
{
string currentUrl = item.SubItems[1].Text;
int i = item.Index;
tasks.Add(Task.Factory.StartNew(() => {
//return GetWebResponse(currentUrl, i);
int statusCode = 0;
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(currentUrl);
Task<HttpWebResponse> responseTask = Task.Factory.FromAsync<HttpWebResponse>(httpWebRequest.BeginGetResponse, asyncResult => (HttpWebResponse)httpWebRequest.EndGetResponse(asyncResult), null);
statusCode = (int)responseTask.Result.StatusCode;
object[] invParams = new object[2];
invParams[0] = i;
invParams[1] = statusCode;
if (InvokeRequired)
{
BeginInvoke(new delUpdateListView(UpdateListView), invParams);
}
else
{
Invoke(new delUpdateListView(UpdateListView), invParams);
}
return statusCode;
}));
}
Task.WaitAll(tasks.ToArray());
}
public void UpdateListView(int itemIndex, int statusCode) {
listView1.Items[itemIndex].ImageKey = "green";
}
I see several problems here:
1) I don't see why GetWebResponse needs to be static. The easiest solution would be to make it an instance method.
2) Why are you using the background worker anyway?
3) It doesn't make much sense to use Tasks and then wait for them to finish right after you spawn them. This blocks your application where it should be responsive.
As for 3): To keep the UI responsive and updatable, disable everything the user may not click before spawning the tasks, add a continuation action to each task that re-enables the UI components. The task may update the list using the usual Invoke calls.
So I can't say for sure this is the issue but I'm just about positive it is. I have a recordset of IVR calls to make. I put the data for each one in a concurrent queue and start 5 background workers to start working from the queue. However, after making 2 calls, the calls stop coming until one person hangs up, then it moves on to call number 3,4,5 etc. Are the any issues with this code?
It seems like the background workers are blocking eachother from calling the same method...? Is that possible?
private ConcurrentQueue<DataTable> _ivrCallsQueue = new ConcurrentQueue<DataTable>();
private List<BackgroundWorker> _ivrCallers = new List<BackgroundWorker>();
public overrid void Process()
{
foreach(DataRow row in _tblRecordsToProcess.Rows)
{
_workingActionItem = actionItemDAL.GetActionItemFromId(Convert.ToInt32(row["FNActionItemId"].ToString()));
var workingActionItemsTable = actionItemDAL.GetActionItemParamValues(Convert.ToInt32(row["FNActionItemId"].ToString()));
ivrCallsQueue.Enqueue(workingActionItemsTable);
}
StartCalls();
while (_ivrCallers.Count != 0)
{
testurls = testurls;
}
}
private void StartCalls()
{
int maxLines = 5;
if (_ivrCallsQueue.Count < maxLines)
{
maxLines = _ivrCallsQueue.Count;
}
for (int i = 0; i < maxLines; i++)
{
DataTable workingCall = new DataTable();
_ivrCallsQueue.TryDequeue(out workingCall);
BackgroundWorker ivrCaller = new BackgroundWorker();
_ivrCallers.Add(ivrCaller);
ivrCaller.DoWork += delegate(object sender, DoWorkEventArgs e)
{
RequestIVR(workingCall, Convert.ToInt32(workingCall.Rows[2][0].ToString()));
_ivrCallers.Remove(ivrCaller);
};
ivrCaller.RunWorkerCompleted += (bw_AnalyzeResults);
ivrCaller.RunWorkerAsync();
}
}
private void bw_AnalyzeResults(object sender, RunWorkerCompletedEventArgs e)
{
DataTable workingCall = new DataTable();
if (_ivrCallsQueue.Count != 0)
{
_ivrCallsQueue.TryDequeue(out workingCall);
BackgroundWorker ivrCaller = new BackgroundWorker();
ivrCaller.DoWork += delegate(object completeSender, DoWorkEventArgs completeArgs)
{
RequestIVR(workingCall, Convert.ToInt32(workingCall.Rows[2][0].ToString()));
_ivrCallers.Remove(ivrCaller);
};
ivrCaller.RunWorkerCompleted += (bw_AnalyzeResults);
ivrCaller.RunWorkerAsync();
}
else
{
}
}
private void RequestIVR(DataTable workingTable,int fnActionItemID)
{
var urlRequest = "http://uccx_http_trigger:9080/test?strTestMode=1&strTaskID=" + fnActionItemID;
var webClient = new WebClient { UseDefaultCredentials = true, Proxy = WebRequest.DefaultWebProxy };
DecodeResponseType(GetValueFromElement("Response Code was ", webClient.DownloadString(urlRequest)));
}
This will spawn at most five threads that each attempt to pull the next item from the queue and process it. If the queue is empty the attempt will fail and the thread will simply exit:
private List<System.Threading.Thread> Threads = new List<System.Threading.Thread>();
private ConcurrentQueue<DataTable> _ivrCallsQueue = new ConcurrentQueue<DataTable>();
private void StartCalls()
{
int maxLines = Math.Min(5 , _ivrCallsQueue.Count);
for (int i = 0; i < maxLines; i++ )
{
System.Threading.Thread T = new System.Threading.Thread(delegate()
{
DataTable workingCall;
while (_ivrCallsQueue.TryDequeue(out workingCall))
{
RequestIVR(workingCall, Convert.ToInt32(workingCall.Rows[2][0].ToString()));
}
});
Threads.Add(T);
T.Start();
}
}
The threads will keep running until all the items have been processed.
It looks like bw_AnalyzeResults does pretty much the same thing that StartCalls() does. In other words, when the background worker has finished its work, you immediately enqueue the same work to happen again, recursively forever?
By the looks of it, you want bw_AnalyzeResults to analyze the results returned by calling your web service. That is not what is happening at the moment.
The code below taken from the bw_AnalyzeResults event handler is scheduling a background job and making itself handle the RunWorkerCompleted event. So, presumably the software keeps going around and around executing bw_AnalyzeResults forever until you kill the process?
private void bw_AnalyzeResults(object sender, RunWorkerCompletedEventArgs e)
{
ivrCaller.DoWork += delegate(object completeSender, DoWorkEventArgs completeArgs)
{
RequestIVR(workingCall, Convert.ToInt32(workingCall.Rows[2][0].ToString()));
_ivrCallers.Remove(ivrCaller);
};
ivrCaller.RunWorkerCompleted += (bw_AnalyzeResults);
}
I was trying to solve the problem in this Question but I ended up having another problem
in short words that question was asking how to load a huge file into textBox chunk by chunk,
so in back ground worker Do_work event I did this:
using (FileStream fs = new FileStream(#"myFilePath.txt", FileMode.Open, FileAccess.Read))
{
int bufferSize = 50;
byte[] c = null;
while (fs.Length - fs.Position > 0)
{
c = new byte[bufferSize];
fs.Read(c , 0,c.Length);
richTextBox1.AppendText(new string(UnicodeEncoding.ASCII.GetChars(c)));
}
}
that didn't work because a backgroundWorker can't affect UI elements and I need to use BeginInvoke to do it.
so I changed the code:
delegate void AddTextInvoker();
public void AddText()
{
using (FileStream fs = new FileStream(#"myFilePath.txt", FileMode.Open, FileAccess.Read))
{
int bufferSize = 50;
byte[] c = null;
while (fs.Length - fs.Position > 0)
{
c = new byte[bufferSize];
fs.Read(c , 0,c.Length);
richTextBox1.AppendText(new string(UnicodeEncoding.ASCII.GetChars(c)));
}
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.BeginInvoke(new AddTextInvoker(AddText));
}
there are two problems with this code.
1- it's taking longer and longer time to append the text (I think because of string immutability replacing the text over time will take longer)
2- on every addition the richTextBox will scroll down to the end which causing application hang.
the question is what can I do to stop the scrolling and application hang?
and what can I do to enhance string concatenation here?
Edit: after some testing and using Matt's answer I got this:
public void AddText()
{
using (FileStream fs = new FileStream(#"myFilePath.txt", FileMode.Open, FileAccess.Read))
{
int bufferSize = 50;
byte[] c = null;
while (fs.Length - fs.Position > 0)
{
c = new byte[bufferSize];
fs.Read(c , 0,c.Length);
string newText = new string(UnicodeEncoding.ASCII.GetChars(c));
this.BeginInvoke((Action)(() => richTextBox1.AppendText(newText)));
Thread.Sleep(5000); // here
}
}
}
when the loading pauses I can read and write without problems or hanging, once the text exceeded the the richTextBox size the loading will scroll down and will prevent me from continue.
One problem I see is that your background worker is, well, not doing any work in the background. It's all running on the UI thread. This may be why the UI thread is non-responsive.
I would refine your DoWork handler like so:
public void AddText()
{
using (FileStream fs = new FileStream(#"myFilePath.txt",
FileMode.Open, FileAccess.Read))
{
int bufferSize = 50;
byte[] c = null;
while (fs.Length - fs.Position > 0)
{
c = new byte[bufferSize];
fs.Read(c , 0,c.Length);
string newText = new string(UnicodeEncoding.ASCII.GetChars(c));
this.BeginInvoke((Action)(() => richTextBox1.AppendText(newText));
}
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
AddText();
}
What I've done is localized the use of BeginInvoke to the single UI call made in the handler. That way, all of the other work is done in the background thread. Maybe that will help with the UI thread becoming non-responsive.
Just call Application.DoEvents. That's the simplest thing, no need to worry about manually creating or synchronizing threads or background workers, yet your app stays responsive.
Also, try using File.ReadLines, which is a lazy-loaded enumerable, rather than manually using a FileStream. This, for example, works for me, and gives you everything you need in a loop and two lines of code.
private void button1_Click(object sender, EventArgs e)
{
foreach (var line in File.ReadLines(#"C:\Users\Dax\AppData\Local\Temp\dd_VSMsiLog0D85.txt", Encoding.ASCII))
{
richTextBox1.AppendText(line + "\r\n");
Application.DoEvents();
}
}
Alternately you can specify your chunk size and load it by that. This will run a bit faster, but take a bit longer (less than a second though) to read the full file at first.
private void button1_Click(object sender, EventArgs e)
{
var text = File.ReadAllText(#"C:\Users\Dax\AppData\Local\Temp\dd_VSMsiLog0D85.txt", Encoding.ASCII);
const int chunkSize = 1000000;
for (var i = 0; i < text.Length / chunkSize; ++i)
{
richTextBox1.AppendText(text.Substring(chunkSize * i, chunkSize));
Application.DoEvents();
}
}
Try this third option and see if your hang is caused by the file or by the loop:
void button1_Click(object sender, EventArgs e)
{
while(true)
{
richTextBox1.AppendText("a");
Application.DoEvents();
}
}
Ok that's not exactly the best solution but it do what I want, instead of using AppendText which will surly scroll down I used +=, and I still got the hang so needed the Sleep(100)
public void AddText()
{
using (FileStream fs = new FileStream(#"myFilePath.txt", FileMode.Open, FileAccess.Read))
{
int bufferSize = 50;
byte[] c = null;
while (fs.Length - fs.Position > 0)
{
c = new byte[bufferSize];
fs.Read(c , 0,c.Length);
string newText = new string(UnicodeEncoding.ASCII.GetChars(c));
this.BeginInvoke((Action)(() => richTextBox1.Text += newText));
Thread.Sleep(100);
}
}
}
this will actually not allow me to scroll down until loading is done,
I couldn't come up with a better idea.
Edit: And a work around to be able to read the text before loading is done is to set scrollBasrs on richTextBox to None and set the form autoscroll to true and in TextChanged event:
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
using (Graphics g = CreateGraphics())
{
SizeF size = g.MeasureString(richTextBox1.Text, richTextBox1.Font);
richTextBox1.Width = (int)Math.Ceiling(size.Width) >
richTextBox1.Width ? (int)Math.Ceiling(size.Width) : richTextBox1.Width;
richTextBox1.Height = (int)Math.Ceiling(size.Height) >
richTextBox1.Height ? (int)Math.Ceiling(size.Height) : richTextBox1.Height;
}
}
this way I will be able to scroll down while loading. I hope someone find a better solution.
Try this:
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
using (FileStream fs = new FileStream(
#"myFilePath.txt", FileMode.Open, FileAccess.Read))
{
int bufferSize = 50;
byte[] c = null;
while (fs.Length - fs.Position > 0)
{
c = new byte[bufferSize];
fs.Read(c, 0, c.Length);
Invoke(new Action(() =>
richTextBox1.AppendText(
new string(UnicodeEncoding.ASCII.GetChars(c)))));
}
}
}
The problem was the BeginInvoke, that call the AppendText async, see
http://msdn.microsoft.com/en-us/library/0b1bf3y3.aspxhttp://msdn.microsoft.com/en-us/library/0b1bf3y3.aspx
and
http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
Keep in mind that the Invoke method can throw exception, eg: if you close the form while loading text, so put it in try catch block and handle it.
BackgroundWorker CAN affect elements on the UI thread, since its events 'ProgressChanged' and 'RunWorkerCompleted' are executed on the calling thread. The following example increments an integer variable up to 10 on a separate thread, and communicates each increment back to the UI thread.
BackgroundWorker _worker;
private void button1_Click(object sender, EventArgs e)
{
_worker.RunWorkerAsync();
}
private void Form1_Load(object sender, EventArgs e)
{
_worker = new BackgroundWorker();
_worker.WorkerReportsProgress = true;
_worker.DoWork += new DoWorkEventHandler(worker_DoWork);
_worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
for (int i = 0; i < 10; i++)
{
// pass value in parameter userState (2nd parameter), since it can hold objects
worker.ReportProgress(0, i); // calls ProgressChanged on main thread
}
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// get value, passed in DoWork, back from UserState
richTextBox1.AppendText(e.UserState.ToString());
}
I'm stuck trying to update a progressbar from other threads ran in a different class. To explain what I do I think a picture will be better. I want to update the progressbar in the //HERE point :
I've tried using a delegate, tried with ReportProgress and I think i've basically tried to use everything google reported in the first 100 results, without success. I'm still learning WPF and this might be silly way to proceed, i'm looking for a quick and dirty way to get the work done but feel free to tell me what I should redesign for a cleaner application.
EDIT : More code.
In ExecutorWindow.xaml.cs :
public void RunExecutor()
{
// CREATE BACKGROUNDWORKER FOR EXECUTOR
execBackground.DoWork += new DoWorkEventHandler(execBackground_DoWork);
execBackground.RunWorkerCompleted += new RunWorkerCompletedEventHandler(execBackground_RunWorkerCompleted);
execBackground.ProgressChanged += new ProgressChangedEventHandler(execBackground_ProgressChanged);
execBackground.WorkerReportsProgress = true;
execBackground.WorkerSupportsCancellation = true;
// RUN BACKGROUNDWORKER
execBackground.RunWorkerAsync();
}
private void execBackground_DoWork(object sender, DoWorkEventArgs e)
{
myExecutor = new Executor(arg1, arg2);
myExecutor.Run();
}
private void execBackground_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("RunWorkerCompleted execBackground");
}
private void execBackground_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ExecutorProgressBar.Value += 1;
}
// TESTING
private void updateProgressBar(int i)
{
ExecutorProgressBar.Value += i;
}
public delegate void callback_updateProgressBar(int i);
In Executor.cs :
public void Run()
{
string[] options = new string[2];
int i = 0;
while (LeftToRun > 0)
{
if (CurrentRunningThreads < MaxThreadsRunning)
{
BackgroundWorker myThread = new BackgroundWorker();
myThread.DoWork += new DoWorkEventHandler(backgroundWorkerRemoteProcess_DoWork);
myThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorkerRemoteProcess_RunWorkerCompleted);
myThread.ProgressChanged += new ProgressChangedEventHandler(backgroundWorkerRemoteProcess_ProgressChanged);
myThread.WorkerReportsProgress = true;
myThread.WorkerSupportsCancellation = true;
myThread.RunWorkerAsync(new string[2] {opt1, opt2});
// HERE ?
CurrentRunningThreads++;
i++;
LeftToRun--;
}
}
while (CurrentRunningThreads > 0) { }
logfile.Close();
MessageBox.Show("All Tasks finished");
}
private void backgroundWorkerRemoteProcess_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker myBackgroundWorker = sender as BackgroundWorker;
string[] options = (string[])e.Argument;
string machine = options[0];
string script = options[1];
// UPDATE HERE PROGRESSBAR ?
RemoteProcess myRemoteProcess = new RemoteProcess(machine, script);
string output = myRemoteProcess.TrueExec();
// UPDATE HERE PROGRESSBAR ?
this.logfile.WriteLine(output);
}
private void backgroundWorkerRemoteProcess_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
CurrentRunningThreads--;
}
private void backgroundWorkerRemoteProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//myExecWindow.ExecutorProgressBar.Value = e.ProgressPercentage; // TESTING
//ExecutorWindow.callback_updateProgressBar(1); // TESTING
}
EDIT 2 : I got it! Simple in fact, but i guess I've been looking too close to find out.
In my ExecutorWindow class :
private void execBackground_DoWork(object sender, DoWorkEventArgs e)
{
myExecutor = new Executor(arg1, arg2);
myExecutor.Run(sender);
}
private void execBackground_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ExecutorProgressBar.Value += 1;
}
And in my Executor class :
private BackgroundWorker myExecutorWindow;
[...]
public void Run(object sender)
{
myExecutorWindow = sender as BackgroundWorker;
string[] options = new string[2];
int i = 0;
while (LeftToRun > 0)
{
if (CurrentRunningThreads < MaxThreadsRunning)
{
BackgroundWorker myThread = new BackgroundWorker();
myThread.DoWork += new DoWorkEventHandler(backgroundWorkerRemoteProcess_DoWork);
myThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorkerRemoteProcess_RunWorkerCompleted);
myThread.ProgressChanged += new ProgressChangedEventHandler(backgroundWorkerRemoteProcess_ProgressChanged);
myThread.WorkerReportsProgress = true;
myThread.WorkerSupportsCancellation = true;
myThread.RunWorkerAsync(new string[2] {opt1, opt2});
CurrentRunningThreads++;
i++;
LeftToRun--;
}
}
[...]
private void backgroundWorkerRemoteProcess_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker myBackgroundWorker = sender as BackgroundWorker;
myBackgroundWorker.ReportProgress(1);
// PROCESSING MY STUFF HERE
myBackgroundWorker.ReportProgress(1);
}
private void backgroundWorkerRemoteProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myExecutorWindow.ReportProgress(1);
}
Thank you !
You can run any method on the UI thread with this very basic sample
this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
{
this.progressBar.Value= 20; // Do all the ui thread updates here
}));
Running commands inside the Dispatcher.Invoke(...), you can actually interact with the UI from any worker thread, where otherwise you would get an exception.
If you really need to have the ultimate control on the background threads & main (UI) thread updates, here is a fantastic tutorial on that: http://blog.decarufel.net/2009/03/good-practice-to-use-dispatcher-in-wpf.html
You should be able to use the Dispatcher.Invoke method
e.g.
Dispatcher.Invoke(
new System.Action(() => myProgressBar.Value = newValue)
);
I got it! Simple in fact, but i guess I've been looking too close to find out.
In my ExecutorWindow class :
private void execBackground_DoWork(object sender, DoWorkEventArgs e)
{
myExecutor = new Executor(arg1, arg2);
myExecutor.Run(sender);
}
private void execBackground_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ExecutorProgressBar.Value += 1;
}
And in my Executor class :
private BackgroundWorker myExecutorWindow;
[...]
public void Run(object sender)
{
myExecutorWindow = sender as BackgroundWorker;
string[] options = new string[2];
int i = 0;
while (LeftToRun > 0)
{
if (CurrentRunningThreads < MaxThreadsRunning)
{
BackgroundWorker myThread = new BackgroundWorker();
myThread.DoWork += new DoWorkEventHandler(backgroundWorkerRemoteProcess_DoWork);
myThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorkerRemoteProcess_RunWorkerCompleted);
myThread.ProgressChanged += new ProgressChangedEventHandler(backgroundWorkerRemoteProcess_ProgressChanged);
myThread.WorkerReportsProgress = true;
myThread.WorkerSupportsCancellation = true;
myThread.RunWorkerAsync(new string[2] {opt1, opt2});
CurrentRunningThreads++;
i++;
LeftToRun--;
}
}
[...]
private void backgroundWorkerRemoteProcess_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker myBackgroundWorker = sender as BackgroundWorker;
myBackgroundWorker.ReportProgress(1);
// PROCESSING MY STUFF HERE
myBackgroundWorker.ReportProgress(1);
}
private void backgroundWorkerRemoteProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myExecutorWindow.ReportProgress(1);
}
I found a really simple solution to create a thread to run any block of code as well as handle Invocation back to the main thread to change the control's properties. It works out of the box with .NET 4.5 and the lambda call on the Dispatcher could be adapted to work with earlier versions of .NET. The main benefit is it's just so blissfully simple and perfect when you just need a quick thread for some really basic bit of code.
So presuming you have a progress bar somewhere on your dialog in scope do this:
progBar.Minimum = 0;
progBar.Maximum = theMaxValue;
progBar.Value = 0;
Dispatcher disp = Dispatcher.CurrentDispatcher;
new Thread(() => {
// Code executing in other thread
while (progBar.Value < theMaxValue)
{
// Your application logic here
// Invoke Main Thread UI updates
disp.Invoke(
() =>
{
progBar.Value++;
}
);
}
}).Start();
You also need to ensure you have a reference to WindowsBase.dll
If you want a more reusable snippet of code running as the thread start you could use a method as the delegate but I find the inline lambda so easy for simple tasks and you don't need to deal with events as with the Background Worker approaches.
I have written code to save an image which is generated by the application. The size of the image is around 32-35 MB. While saving the image to a BMB file, it is taking a long time, around 3-5 secs. For this purpose, I have used a background worker but when running the background worker, it shows an error like..."can't access the object as it is created on different thread".
Following is the code:
private void btnSaveDesign_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog sfd = new Microsoft.Win32.SaveFileDialog();
sfd.Title = "Save design as...";
sfd.Filter = "BMP|*.bmp";
if (sfd.ShowDialog() == true)
{
ww = new winWait();
ww.Show();
System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
fName = sfd.FileName;
cache = new CachedBitmap((BitmapSource)imgOut.Source, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
bw.RunWorkerAsync();
}
}
void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
ww.Close();
}
void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache)); //here... it says cant access...
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
I have declared "cache" as a global object. (A similar trick worked when I was programming in Windows Forms with VB.NET.)
ww is the wait window that I want to be displayed while the precess is being executed.
How to do this? Is there any other simple method for multi threading in WPF?
When WPF objects are created they are assigned to a Dispatcher object. This disallows any threads other than the creating thread to access the object. This can be circumvented by freezing the object by calling the freeze method. You would need to call Freeze on your bitmapsource object. Once you have frozen your object it becomes uneditable
Your problem comes about because you are accessing an object which is not created by the background worker thread. Normally this would happen if you access a UI control which is created in the main thread and accessed from different thread.
Use the code below.
Dispatcher.Invoke
(
new Action(
delegate()
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache));
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
)
);
I think you have to pass cache as a parameter to the new thread:
bw.RunWorkerAsync(cache);
and get it from the DoWork method:
var cache=(CacheType) e.Argument;
.NET framework provides a simple way to get started in threading with
the BackgroundWorker component. This wraps much of the complexity and
makes spawning a background thread relatively safe. In addition, it
allows you to communicate between your background thread and your UI
thread without doing any special coding. You can use this component
with WinForms and WPF applications. The BackgroundWorker offers
several features which include spawning a background thread, the
ability to cancel the background process before it has completed, and
the chance to report the progress back to your UI.
public BackgroudWorker()
{
InitializeComponent();
backgroundWorker = ((BackgroundWorker)this.FindResource("backgroundWorker"));
}
private int DoSlowProcess(int iterations, BackgroundWorker worker, DoWorkEventArgs e)
{
int result = 0;
for (int i = 0; i <= iterations; i++)
{
if (worker != null)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return result;
}
if (worker.WorkerReportsProgress)
{
int percentComplete =
(int)((float)i / (float)iterations * 100);
worker.ReportProgress(percentComplete);
}
}
Thread.Sleep(100);
result = i;
}
return result;
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
int iterations = 0;
if (int.TryParse(inputBox.Text, out iterations))
{
backgroundWorker.RunWorkerAsync(iterations);
startButton.IsEnabled = false;
cancelButton.IsEnabled = true;
outputBox.Text = "";
}
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
// TODO: Implement Cancel process
this.backgroundWorker.CancelAsync();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// e.Result = DoSlowProcess((int)e.Argument);
var bgw = sender as BackgroundWorker;
e.Result = DoSlowProcess((int)e.Argument, bgw, e);
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
workerProgress.Value = e.ProgressPercentage;
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
outputBox.Text = "Canceled";
workerProgress.Value = 0;
}
else
{
outputBox.Text = e.Result.ToString();
workerProgress.Value = 0;
}
startButton.IsEnabled = true;
cancelButton.IsEnabled = false;
}