this is probably a nobraner, but I can't figure it out.
I've got this function which converts wav files to mp3. I call this from my button_click handle. The thing is, when the conversion is started, the rest of the code in the button_click handler continue, while the conversion is happening in a different thread.
Now, I need the rest of the code in the button_click handle so continue to try until a boolean is true, so that I know that the conversion is done before the rest of the code continues.
I've tried using Do While but it didn't seem to do the trick. Perhaps it's just me though..
Is this a client application? Sounds like a great application for BackgroundWorker.
To execute a time-consuming operation
in the background, create a
BackgroundWorker and listen for events
that report the progress of your
operation and signal when your
operation is finished.
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler
(bw_RunWorkerCompleted);
....
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync(filename);
}
static void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
string filename = (string)e.Argument;
e.Result = DoConversion(filename);
}
static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label.Text = "Done: " + e.Result.ToString();
DoSomethingWhenConversionComplete();
}
This is called a spin-wait and is not the best way to accomplish your task:
// IsConversionComplete will be set by some other thread
while(!IsConversionComplete){
Thread.Sleep(100);
}
// carry on
A much more efficient solution requires a synchronization structure like a mutex or use of events.
Related
I have the following constellation:
MainForm.cs -> Including all my Form Elements
Program.cs -> includes the main part, which is a xmlreader/writer to alter xml attributes in xml files that can be as large as 4gb
So this little app works but of course the UI gets unresponsive and freezes which I want to avoid, I also hope to reduce the duration of this process on the way
I start the call of my xmlread/write method from a BtnClick event:
void BtnApplyChangesClick(object sender, EventArgs e)
{
Program p = Program.Instance;
pbApplyChanges.Minimum = 0;
pbApplyChanges.Step = 1;
Cursor.Current = Cursors.WaitCursor;
foreach(DataGridViewRow cr in dataGridView2.Rows)
{
pbApplyChanges.Maximum = dataGridView2.Rows.Count;
p.changeElements(cr.Cells["Filename"].Value.ToString(), txtTenant.Text, txtDate.Text, txtEvtId2.Text);
pbApplyChanges.PerformStep();
}
Cursor.Current = Cursors.Arrow;
MessageBox.Show("Job done");
}
In the call I use my singleton instance of Program.cs and my main Method there (changeElements) uses 4 String params, that are all taken from information in the Form! (I suppose this is kinda bad practice but it worked so far...)
When I tried to replace this method call with a backgroundWorker (itself made the method call then) I failed as the method call wasn't even made... I found out that UI elements can't be accessed from the BW thread, so I suppose this is also the reason for my method call not working?!
So how can I get this constellation to work? Do I have to pass all 4 string Params AND the class instance (of Program.cs) to the background worker? Is BW even the best tool for the job?
In general the BackgroundWorker shouldn't access any UI-Elements. It's an old advice in Winforms that accessing UI-Elements should just happen from the UI-Thread.
You can use the Background-Worker like this:
private void Main(string[] args)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += Bw_DoWork;
bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
//Parameter you need to work in Background-Thread for example your strings
string[] param = new[] {"Text1", "Text2", "Text3", "Text4"};
//Start work
bw.RunWorkerAsync(param);
}
//Do your Background-Work
private void Bw_DoWork(object sender, DoWorkEventArgs e)
{
string[] param = e.Argument as string[];
//Process your long running task
e.Result = null; //Set your Result of the long running task
}
//Taking your results
private void Bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Apply your Results to your GUI-Elements
myTextBox1.Text = e.Result.ToString();
}
Background-Worker is some old school stuff by the way, so if you like to learn something new take a look here and inform yourself about the TPL. This gives you a better handling of asynchronous.
In fact I think it's not really good to store 4gb data in a XML-File. Do you think about a Database? Or split the XML-File in many XML-Files? So you would be able to read data in chunks.
I hope this helps you.
I don't use background worker for this. I use normal threads instead. Try this code:
public void ButtonDoWork_Click(eventArgs......) {
DoWorkThread = new Thread(new ThreadStart(DoWork)); // Setup thread
DoWorkThread.isBackground = true; // Its background so, we need to set background flag
DoWorkThread.Start(); // Start the thread
}
private Thread DoWorkThread: // our Thread object
private void DoWork() { // This void contains action that will be performed by thread
//TODO: Background processing. To update UI from another thread use Control.Invoke(...)
}
Please note, I don't tested this code - I write it from my memory and it's late so it can not work.
You can also read about Threads at MSDN :)
I have a thread which calls one of the methods, now this method executes a query which can take a very long time possibly 40 minutes or so to complete,
I want to give user a a choice to be able to cancel this operation (meaning stop the thread and stop the query to release database).
I should mention that I am developing WPF Application using .net 4.5, SQL SERVER DB and C#.
You should use backgroundworker, it is exactly what you want.
Eather drag and drop it from the toolbox or create it in code - behind. It supports Cancellation, reports progress, notifies when complete and know if it is running or not.
Here is an example.
void method(){
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.ProgressChanged += worker_ProgressChanged;
worker.DoWork += worker_DoWork;
worker.WorkerSupportsCancellation = true;
if(!worker.IsBusy)
{
worker.RunWorkerAsync();
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//do whatever needs to be done on the other thread here.
object argument = e.Argument; //if passed argument in RunWorkerAsync().
object result = new object();
e.Result = result;
//after making worker global, you can report progress like so:
worker.ReportProgress(50); //you can also pass a userState, which can be any object, to show some data already.
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//you can update a progress bar in here
int progress = e.ProgressPercentage;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//when done
}
void CancelTheTask()
{
if (worker.IsBusy)
{
//make worker global first, but then
worker.CancelAsync();
}
}
A important things to look at: Never use resources in the DoWork method that are not created inside it. Thus pass things you need in the background worker as Arguments. And things that are created by the backgroundworker should not be set to a global variable ether, pass by result.
When cancelling, RunWorkCompleted will also be fired. Now the query to the database is already being executed, so that is still running, even when your application lost all resources to it.
To cancel that, we would need to know how you execute the query, like #S.Akbari mentioned is one way. Entity Framework 6 also supports cancellation.
For that: check this when using Queryable
here is another example
Or this solution without Entity Framework.
Using Task Parallel Library (TPL) you can use the Task Cancellation pattern.
When you have your Thread blocked on waiting for the query, it's useless for stopping anything.
Make sure the SqlConnection of the query is accessible from your UI and Close it. Abandon the Thread, it will terminate (with an error you've got to suppress).
If the UI thread is doing a Long-time operation it won't be able to process
UI requests. This is also known as Not Responding.
Use ThreadPool like this:
CancellationTokenSource ct;//instantiate it before ThreadPool.QueueUserWorkItem line
private void operation_Click(object sender, RoutedEventArgs e)
{
ct = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem(_ =>
{
var result = LongTimeOperation();//set the operation in another thread so that the UI thread is kept responding
//use the Dispatcher to "return" to the UI thread
Dispatcher.BeginInvoke(new Action(() =>
{
//Use result for example : Label1.Text = result.ToString();
}));
});
}
To give user a choice to be able to cancel the operation use CancellationTokenSource like this:
private void cancel_Click(object sender, RoutedEventArgs e)
{
if (ct != null)
{
ct.Cancel();
ct= null;
}
}
Note: in LongTimeOperation() you must have one more parameter of type CancellationToken
private float LongTimeOperation(CancellationToken ct)
{
if (ct.IsCancellationRequested)
return -1;
....
....
}
This link is useful about Cancellation in Managed Threads.
this is a common problem.But in WPF and WinForm, i'd like to use BackGroundWorker. See Here
I have an BackgroundWorker:
BackgroundWorker worker;
private void Form1_Load(object sender, EventArgs e)
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged +=
new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}
DoWork Event
void worker_DoWork(object sender, DoWorkEventArgs e)
{
int percentFinished = (int)e.Argument;
while (!worker.CancellationPending && percentFinished < 100)
{
percentFinished++;
worker.ReportProgress(percentFinished);
//here I start my operation
//operation....
//operation end
}
e.Result = percentFinished;
}
Progresschanged
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
Completed
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Asynchroner Thread kam bis zum Wert:
"+e.Result.ToString());
btnStartEnd.Text = "Starten";
}
And finally my button:
private void btnStartEnd_Click(object sender, EventArgs e)
{
if (worker.IsBusy)
{
worker.CancelAsync();
btnStartEnd.Text = "Starten";
}
else
{
if (progressBar1.Value == progressBar1.Maximum)
{
progressBar1.Value = progressBar1.Minimum;
}
worker.RunWorkerAsync(progressBar1.Value);
btnStartEnd.Text = "Stoppen";
}
}
This code works but I get a loop for my operations until the percentage is 100, so the operation starts 100 times and so takes 100 times longer.
The goal should be that the operation only starts one time and the percentage counts from 1-100.
Maybe I understand something wrong, but how does the worker know how far the operation is done? That value should be send to the progress bar for visualisation.
Normally you won’t add the loop inside the DoWork method
If you want to load for example 100 files from the file system and report the progress it could look like that:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for(int i = 0; i < 100; i++)
{
// Load file and do something with the content
...
// Report the progress which causes the ProgressChanged event to be fired
// And update progressbar with the UI thread
worker.ReportProgress(i);
}
}
If you only have one long running operation that needs to be executed inside the DoWork method it needs to be executed asynchronously
Here is one example how you could call an action asynchronously in .NET:
Action action = () =>
{
for(int i = 0; i <100; i++)
{
Console.WriteLine(String.Format("Step {0} of long running operation", i));
Thread.Sleep(1000);
}
};
var r = action.BeginInvoke(null, null);
while(!r.IsCompleted)
{
Console.WriteLine("Waiting...");
Thread.Sleep(100);
}
However in .NET there are many more ways to do it. See for example:
http://msdn.microsoft.com/en-us/library/jj152938(v=vs.110).aspx for
Async patterns in .NET
http://msdn.microsoft.com/de-de/library/hh191443.aspx for Async
programming with await/async (new in .NET 4.5)
Maybe I understand something wrong, but how does the worker know how far the operation is done? That value should be send to the progress bar for visualisation.
The BackgroundWorker class doesn't know anything about your operations and about its progress. It's your job to determine when to report the progress.
In general the workflow for a background worker looks like this:
UI thread calls RunWorkerAsync.
DoWork event handler is called on a different thread. During the event handler you can report progress using the ReportProgress method
If you report a progress then the ProgressChanged event handler is called on the UI thread. Here you can update a progress bar for example.
When your event handler for the DoWork event exits the RunWorkerComplete event is raised.
Now why does every example for the BackgroundWorker has a for-loop? Because it's very easy to write, and measuring progress for a for-loop is trivial. Unfortunately this quite often isn't useful for different kind of operations.
If your long running operation processes N files then it's pretty obvious that you can update the progress bar after every item by 1/N. That's what the for-loop example does.
But if you only have one long running operation then you simply don't have any chance to get the progress unless the operation itself supports reporting it or if you can somehow estimate the progress.
The BackgroundWorker can't magically give a long running operation a progress bar. It only enables you to run the operation in the background.
Below is my coding:
Form2 msgForm;
private void button3_Click_1(object sender, EventArgs e)
{
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
//bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
msgForm = new Form2();
try
{
bw.RunWorkerAsync();
msgForm.ShowDialog();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (comboBox15.Text == "")
{
//MessageBox.Show("Please select Printer ID.", "Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
//return;
}
// Coding that transmit protocol and will last around 2 minutes.
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
msgForm.Close();
}
When I run this background worker coding, there's an error stating "Cross-thread operation not valid: Control 'comboBox15' accessed from a thread other than the thread it was created on."
How do I solve this problem guys?
You can use Invoke:
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.comboBox15.InvokeRequired)
{
this.Invoke((MethodInvoker) delegate {if (comboBox15.Text == ""){// What you want to do}});
}
else
{
if (comboBox15.Text == "")
{
}
}
also read the following:
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx
Anonymous method in Invoke call
You can't UI elements from a non-UI-thread. Ideally, provide the relevant information to the background worker before it starts, e.g.
string text = combo15.Text;
bw.DoWork += (sender, args) => TransmitStuff(combo15.Text, args);
...
void TransmitStuff(string printerId, DoWorkEventArgs e)
{
...
}
If you can use .NET 4.5 and C# 5, you could use an async method to quite possibly make all of this easier... but I realize that's unlikely to be an option for you.
EDIT: While you can use Invoke, that ends up being quite messy - and you've got potentially inconsistent state. I generally think it's tidier to work out all the state you need before you start the long-running operation, validate it all, and then hand it to the operation. If you need to update the UI during the operation, you can use the BackgroundWorker progress facilities.
In BackgroundWorker, when we call any user controls its problem. Please use this property in Window Form Load event:
CheckForIllegalCrossThreadCalls = false;
You can only access gui controls from your main thread.
Move the
if (comboBox15.Text == "")
part to button3_click
You can get round it by passing the value such as below.
private void Dowork()
{
backgroundWorker1.RunWorkerAsync(comboBox1.Text);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
String selected = (String)e.Argument;
if (String.IsNullOrEmpty(selected)) return;
//do stuff
}
good evening!
currently i'm developing a wpf-client for some rest-service. the communcation with the rest-service is no problem and is done in an extra assembly (communcation-interface).
basically:
i have a somehow "search"-button which executes a method. this method communicates with the service, updates some textboxes and a progress-bar (to give the user some graphic info, how far we are ...).
unfortunaly the server, which hosts the service is a bit lame, causing some severe response-time (about 4 secs). this, on the other hand, causes my wpf-application to wait, which ends up in: going black, and titeling "not responding" ...
i've already tried to put this execution in another thread, but ... it's logical that i won't get any access to the controls of my wpf-window ...
atm i'm really helpless ... can anyone give me some handeling-routine or a solution?
Your UI thread is busy waiting on a response from the web service, and isn't available to paint the screen. One good option, is push the service request off to another, non-UI thread. Look into BackgroundWorker, which was designed specifically to make this easy. It handles marshalling of cross-thread calls from non-UI to UI threads.
Roughly:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync(arg);
...
static void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
int arg = (int)e.Argument;
e.Result = CallWebService(arg, e);
}
static void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Increment();
}
static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label.Text = "Done: " + e.Result.ToString();
}
To access your controls from a second thread use Dispatcher.BeginInvoke:
Dispatcher.BeginInvoke(new Action(() =>
{
// Update your controls here.
}), null);
Or you can look into using BackgroundWorker.