I have just updated the Visual Studio to 17.4.4.
In the following code Thread fires cross-thread exception but Task doesn't. In the Start Without Debugging, Task still does nothing and Thread halts the application.
How to force Task fires cross-thread exception not to miss it?
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
CheckForIllegalCrossThreadCalls = true;
}
private void button1_Click(object sender, EventArgs e)
{
Task task = new(Method1);
task.Start(); // does not change Text and does not fire exception
}
private void button2_Click(object sender, EventArgs e)
{
Thread thread = new(Method1);
thread.Start(); // fires exception
}
void Method1()
{
Text = "a";
}
This is by design. When a Task fails, the exception is captured in its Exception property. The error is not propagated as an unhandled exception that crashes the process. In case you find the process-crashing behavior desirable, you can make it happen by awaiting the task on the ThreadPool:
ThreadPool.QueueUserWorkItem(async _ => await task);
Related
Please bear with my ignorance on this subject and on the terminology I have used. Please correct me where I am incorrect.
I have used a background Worker from the toolbox onto my form and I am passing a method to the DoWork event. What I have understood in this first attempt with background Worker is that I can use the background Worker that I've created only for 1 task. See code below:
private void btn1_Click(object sender, EventArgs e)
{
// Should call the uploadToDB1 using BackgroundWorker's DoWork event.
backgroundWorker1.RunWorkerAsync();
}
private void btn2_Click(object sender, EventArgs e)
{
// Should call the uploadToDB2 using BackgroundWorker's DoWork event.
backgroundWorker1.RunWorkerAsync();
}
private void uploadToDB1()
{
// Code for uploading to DB 1.
}
private void uploadToDB2()
{
// Code for uploading to DB 2.
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
uploadToDB1(); // I want to change this to uploadToDB2 i.e. a variable method, How do I assign a call to this?
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Visible = true;
progressBar1.Maximum = maxRecords;
lblProgress.Text = Convert.ToString(e.ProgressPercentage.ToString() + "/" + maxRecords);
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Visible = false;
lblProgress.Text = "";
}
I need to be able to dynamically pass a method to the DoWork event without having the need to create multiple background Workers as the actions in the rest of the events related to the background Worker remains unchanged.
Could you please advise how I should go about doing this?
Updated Code using TPL, however I am getting a cross thread error. Could you please help with a corrected code? Upload to DB 2 should happen only after upload to DB 1 is complete. So each time an upload happens the label and progress bar needs to be updated. I also need to pass different text to the label.
private void btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(uploadToDB1);
}
private void uploadToDB1()
{
for(i=0;i<dt.rows.count-1;i++)
{
// Code for inserting into DB1.
progressbar1.maximum=dt.rows.count-1;
progressbar1.value=i;
}
uploadToDB2();
}
private void uploadToDB2()
{
for(i=0;i<dt.rows.count-1;i++)
{
// Code for inserting into DB2.
progressbar1.maximum=dt.rows.count-1;
progressbar1.value=i;
}
}
What you can do, and is a bit of a hack, is pass an Action as an argument for invocation to your DoWorkAsync:
var bw = new BackgroundWorker();
bw.DoWork += (s, o) =>
{
Action actualWork = (Action)o.Argument;
actualWork();
}
and when you invoke DoWorkAsync:
Action action = () => DoSomething();
bw.RunWorkerAsync(action);
Instead, as #Sriram suggested, look into the Task Parallel Library, which will make you life a bit easier:
private async void btn1_Click(object sender, EventArgs e)
{
await Task.Run(UpdateFirst);
// Update UI here.
}
private async void btn2_Click(object sender, EventArgs e)
{
await Task.Run(UpdateSecond);
// Update UI again.
}
An extensive answer on TPL and the use of IProgess<T> can be found in How to provide a feedback to UI in a async method?
I have implemented a delay in process after the user stops typing in the textbox
private System.Timers.Timer timer = new System.Timers.Timer(1000);
public SearchItem(){
timer.AutoReset = false;
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
}
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e){
bindingSource.DataSource = logic.GetData(StockCodeTextBox.Text);
}
private void StockCodeTextBox_TextChanged(object sender, EventArgs e){
timer.Stop();
timer.Start();
if (StockCodeTextBox.Text.Equals("")){
AllItemsGridView.ClearSelection();
return;
}
}
after the user stops typing, why am I getting this error?
An exception of type 'System.InvalidOperationException' occurred
in System.Windows.Forms.dll but was not handled in user code
Additional information: Cross-thread operation not valid: Control
'AllItemsGridView' accessed from a thread other than the thread it was
created on.
Your UI is updated on the WinForms UI Dispatcher thread, while your Timer executes on a background thread. So you can't update the UI from a thread which does not own it. One workaround is to use this extension to update your UI from background thread:
public static class ControlExtension
{
public static void Do<TControl>(this TControl control, Action<TControl> action)
where TControl : Control
{
if (control.InvokeRequired)
control.Invoke(action, control);
else
action(control);
}
}
Sample use:
this.Do(f=>{f.AllItemsGridView.ClearSelection();})
In your code:
private void StockCodeTextBox_TextChanged(object sender, EventArgs e){
timer.Stop();
timer.Start();
this.Do(f=>{
if (f.StockCodeTextBox.Text.Equals("")){
f.AllItemsGridView.ClearSelection();
}
}
}
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e){
this.Do(f=>{
f.bindingSource.DataSource = logic.GetData(f.StockCodeTextBox.Text);
}
}
If you are free to choose a solution, consider rewriting your app with ReactiveUI and .Net 4.5.2.
https://github.com/AdaptiveConsulting/ReactiveTrader
If you are using System.Windows.Forms, you should use the Timer that comes with it so you don't have cross threading issues.
Instead of System.Timers.Timer use System.Windows.Forms.Timer.
I have service call in my form application. When my button has been clicked I am calling my service method. Here is my code block:
void listBox_SelectedValueChanged(object sender, EventArgs e)
{
if (listBox.SelectedItem.Equals("Demo"))
{
progressBar1.Visible = true;
backgroundWorker1.RunWorkerAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
ObservableCollection<FilterInfo> filterInfos = new ObservableCollection<FilterInfo>();
FilterInfo myFilterObj = new FilterInfo("SUPERVISOR", userName);
filterInfos.Add(myFilterObj);
ObservableCollection<DEMOBec> demos = demoSvc.GetDemoByFilter(filterInfos, false);
dt = new Converter<DEMOBec>().ConvertDataTable(demos.ToList());
}
Before calling the service, I make my ProgressBar (Style = Marquee) visible. But I couldn't make it invisible in completed method because of Cross Thread problem.
When I tried to do something with in UI thread in BGWorker's Completed event,
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Visible = false;
}
I am getting an exception :
Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on.
How can I handle this problem?
You need to use Invoke() method
private delegate void ToDoDelegate();
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Invoke(new ToDoDelegate(() => progressBar1.Visible = false));
}
more info for Invoke()
http://msdn.microsoft.com/de-de/library/System.Windows.Forms.Control.Invoke(v=vs.110).aspx
Here's alittle piece of code that I always love to use, I don't remember where I got it from I put it in my Dropbox a long time ago.
public static class ControlExtensions
{
public static TResult InvokeEx<TControl, TResult>(this TControl control,
Func<TControl, TResult> func)
where TControl : Control
{
return control.InvokeRequired
? (TResult)control.Invoke(func, control)
: func(control);
}
public static void InvokeEx<TControl>(this TControl control,
Action<TControl> func)
where TControl : Control
{
control.InvokeEx(c => { func(c); return c; });
}
public static void InvokeEx<TControl>(this TControl control, Action action)
where TControl : Control
{
control.InvokeEx(c => action());
}
}
Usage
this.InvokeEx( x => Progressbar1.Visible = false); //Note 'x' could be any variable
This way you won't have to type all that out each time, and it's so simple to use. Just add this right to the end of your form class (after the last bracket closes). You can use this to preform any cross thread operations safely.
Modify the Visibility of the ProgressBar on the UI thread.
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)
delegate()
{
progressBar1.Visible = false;
});
I think the exception isn't raised in the RunWorkerCompleted event (as in comments said). My suggestion is, that the lines you try to access UI cmponents in the DoWork event fires it.
Try to put the data you need in the DoWork event into DoWorkEventArgs e. Tons of examples how to do it are provided on SO like this discussion.
When the work has finished, provide the "main UI thread" the modified/new data by using event args, too, like this:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
// do some work
e.Result = ... ; // modified/new data
}
and retrieve it in the following way:
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
var newData = (CastToWhatever) e.Result;
}
Hope it helps =)
EDIT
Your code in backgroundWorker1_RunWorkerCompleted() is definitly not the source of the exception. It built a little and simple example to demonstrate my argment:
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync(textBox1.Text);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
var num = Int32.Parse(e.Argument.ToString()) + 1;
backgroundWorker1.ReportProgress(num);
e.Result = num;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = (int)e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
textBox1.Text = e.Result.ToString();
}
What it does? On the WinForm there is a TextBox with a number, a ProgressBar and a Button to start a BackGroundWorker.
The worker gets the number from the TextBox (UI thread), DoWork increases it by 1 and saves it as result (new thread). Additionally it reports the increased number to the ProgressBar (cross-thread).
Finally RunWorkerCompleted reads out the result and stores the new value in the TextBox (UI thread).
So, please re-check your code ;-)
I have an application in which I launch a window that displays byte data coming in from a 3rd party tool. I have included .CancelAsync() and .CancellationPending into my code (see below) but I have another issue that I am running into.
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Thread popupwindow = new Thread(() => test());
popupwindow.Start(); // start test script
if(backgroundWorker.CancellationPending == true)
{
e.Cancel = true;
}
}
private voide window_FormClosing(object sender, FormClosingEventArgs e)
{
try
{
this.backgroundWorker.CancelAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
}
Upon cancelling the test I get an `InvalidOperationException occurred" error from my rich text box in my pop-up window. It states that "Invoke or BeginInvoke" cannot be called on a control until the window handle has been created". I am not entirely sure what that means and would appreciate your help.
LogWindow code for Rich Text Box:
public void LogWindowText(LogMsgType msgtype, string msgIn)
{
rtbSerialNumberValue.Invoke(new EventHandler(delegate
{
rtbWindow.SelectedText = string.Empty;
rtbWindow.SelectionFont = new Font(rtbWindow.SelectionFont, FontStyle.Bold);
rtbWindow.SelectionColor = LogMsgTypeColor[(int)msgtype];
rtbWindow.AppendText(msgIn);
rtbWindow.ScrollToCaret();
}));
}
After reading your code; it appears the background worker completes nearly instantly; likely killing any threads that were spawned from it. More to the point; a background worker that is already stopped will throw "InvalidOperationException" when "CancelAsync" is called.
I'd advise placing any GUI related work into caller instead of the background worker thread. This is an important consideration because you will get cross-thread exceptions and other strange behavior such as rather serious GUI refresh issues.
The background worker "DoWork" method should be considered threaded already. You can see this by adding simple debug statements to your code.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
private void button1_Click(object sender, EventArgs e)
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
backgroundWorker1.RunWorkerAsync();
}
Finally; I'd add that CancellationPending works best when polled in a loop-construct like so,
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
foreach (var workItem in work)
{
workItem.Perform();
if (backgroundWorker1.CancellationPending)
{
break;
}
}
}
Hope this helps.
What's happening is the window handle is already gone before the LogWindowText method is called by the finalize method (RunWorkerCompleted handler) of the background worker. You need to check that Handle:
if (this.Handle == IntPtr.Zero) { return; }
I am using Windows Form application for my thread demo. When I click on button1 ,It will start the thread and recursively doing a work.
Here the Form will not hang as I expected. I want to Stop the currently running thread when I click on Button2. However this won't work.
private void button1_Click(object sender, EventArgs e)
{
t = new Thread(doWork); // Kick off a new thread
t.Start();
}
private void button2_Click(object sender, EventArgs e)
{
t.Abort();
}
static void doWork()
{
while (true)
{
//My work
}
}
}
.When Im debugging, the button2_Click method won't hit the pointer. I think because Thread is keep busy.
Please correct me if I going wrong somewhere.
You can't kill thread like this. The reason is to avoid situations where you add lock in thread and then kill it before lock is released.
You can create global variable and control your thread using it.
Simple sample:
private volatile bool m_StopThread;
private void button1_Click(object sender, EventArgs e)
{
t = new Thread(doWork); // Kick off a new thread
t.Start();
}
private void button2_Click(object sender, EventArgs e)
{
m_StopThread = true;
}
static void doWork()
{
while (!m_StopThread)
{
//My work
}
}