I am using a BackgroundWorker to send asynchronous HTTP requests (with RestSharp by the way) and need to pass the returned data (HTTP response) to the main thread to update some GUI components based on that data. For this I use ReportProgress method like this:
static void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
Object[] args = (Object[])e.Argument;
string strURL = args[0].ToString();
string strParam = args[1].ToString();
for (int i=0; i<5; i++) {
var client = new RestClient(strURL);
var request = new RestRequest(strURL, Method.POST);
request.AddParameter("someparam", strParam);
client.ExecuteAsync(request, response =>
{
string strRet = response.Content;
worker.ReportProgress(i, strArr);
}
}
}
This code will raise an exception on ReporProgress saying that it is illegal to call this method when the Background Worker has already finished its work and it is absolutely correct because by the time I receive HTTP response the bw_DoWork will already have finished executing.
So as you can see I am dealing with an asynchronous task in already asynchronous Background Worker thread.
I know that ReportProgress is marshaled to execute on GUI thread and I don't see any other ways to pass data back to the main form. How can this be fixed/corrected?
You need some way to let your background worker sleep/wait until all ExecuteAsync´s are finished.
You can do it like this.
void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
Object[] args = (Object[])e.Argument;
string strURL = args[0].ToString();
string strParam = args[1].ToString();
int finishedCounter = 0;
// A reset event that is set from the last ExecuteAsync
AutoResetEvent finishedEvent = new AutoResetEvent(false);
for (int i=0; i<5; i++) {
var client = new RestClient(strURL);
var request = new RestRequest(strURL, Method.POST);
request.AddParameter("someparam", strParam);
client.ExecuteAsync(request, response =>
{
string strRet = response.Content;
worker.ReportProgress(i, strArr);
// Check if this is the last worker/step then
// wake up the background worker to finish the do_work method
if (Interlocked.Increment(ref finishedCounter) == 5)
finishedEvent.Set();
});
}
// wait till work is done
finishedEvent.WaitOne();
}
An alternative would be to not use a BackgroundWorker at all.
You can update the UI in WPF using the MainWindow´s dispatcher.
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
// Update UI
});
or
System.Windows.Application.Current.MainWindow.Dispatcher.Invoke(() =>
{
// Update UI
});
Or in Windows Forms using a concrete Form
System.Windows.Forms.Form form = ...;
form.Invoke(new Action( () =>
{
//Update UI
}));
Related
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);
}
After I clicked the "Start" button in WPF, the program went to TPL part. The main window was freezing then.
private void Start_Click(object sender, RoutedEventArgs e)
{
var producer = Producer();
var consumer = Consumer();
Task.WaitAll(producer, consumer);
}
async Task Producer()
{
try
{
// add items to the queue
async Task Consumer()
{
try
{
var executionDataflowBlockOptions = new ExecutionDataflowBlockOptions
{
//TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(),
MaxDegreeOfParallelism = 4
};
var consumerBlock = new ActionBlock<AppointmentReminder>(
r=>
{
string s = RunScript(r);
Dispatcher.Invoke((Action)delegate()
{
slider.Value = slider.Value - 1; // update the slider value;
});
},executionDataflowBlockOptions);
// m_Queue is bufferBlock
m_Queue.LinkTo(
consumerBlock, new DataflowLinkOptions { PropagateCompletion = true });
await Task.Delay(500);
}
Questions:
How to deal with the window freezing? If I uncomment out TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(), it is still freezing and the customer part is never reached.
I want to update the slider by slider.Value = slider.Value - 1; it failed as well.
Task.WaitAll waits synchronously while blocking the calling thread. In your case that would block the GUI thread "freezing" your windows and may even lead to a deadlock.
Use await Task.WhenAll in an async void event handler (the only case where async void is acceptable) to asynchronously wait without blocking a thread:
private async void Start_Click(object sender, RoutedEventArgs e)
{
var producer = Producer();
var consumer = Consumer();
await Task.WhenAll(producer, consumer);
}
I have a button named submit_Button which has the following code in the OnClicked event:
Thread thread = new Thread(new ThreadStart(heavyWork));
thread.Start();
heavyWork function code :
private void heavyWork()
{
DisableUI();
string Name = Name_textBox.Text;
celebrityName = Name.Replace(" ", "+");
string queryURL = "http://stackoverflow.com";
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(queryURL);
request.Method = "GET";
// make request for web page
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader htmlSource = new StreamReader(response.GetResponseStream());
string htmlStringSource = string.Empty;
htmlStringSource = htmlSource.ReadToEnd();
response.Close();
//var regex = new Regex(#"<FONT class=""result"">(.*?)</FONT>");
var regex = new Regex(#"<span class=""kno-a-v"">(.*?)</span>");
var match = regex.Match(htmlStringSource);
var result = match.Groups[1].Value;
result = HttpUtility.HtmlDecode(result);
MessageBox.Show(result);
EnableUI();
}
// Functions
private void DisableUI()
{
celebrityName_textBox.IsEnabled = false;
submit_Button.IsEnabled = false;
infoType_listBox.IsEnabled = false;
preloader_Image.Visibility = Visibility.Visible;
}
private void EnableUI()
{
celebrityName_textBox.IsEnabled = true;
submit_Button.IsEnabled = true;
infoType_listBox.IsEnabled = true;
preloader_Image.Visibility = Visibility.Hidden;
}
When I run the application, then press the button, the application crashes immediately!
What's happening ? I tried to use BackgroundWorker Instead, but when I can worker.RunWorkerAsync() nothing happens ( the worker doesn't start ).
DisableUI() is changing the state of UI controls on a thread which is not the UI thread. This is disallowed in WPF, because it is a single threaded UI framework, and only allows you to change controls on something called the UI thread. Dispatcher.Invoke()/BeginInvoke() is your friend.
Good MSDN article on using the Dispatcher
You are going to want to do any UI related stuff on the UI thread, you can do this with the dispatcher, like so:
private void heavyWork()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(DisableUI));
//rest of method
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnableUI));
}
When using Background Worker your code should look like something like this:
Notice that when you start worker.RunWorkerAsync() method BackgroundWorkerDoWork is called
BackgroundWorker _backgroundWorker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_backgroundWorker.DoWork += BackgroundWorkerDoWork;
_backgroundWorker.RunWorkerCompleted += BackgroundWorkerRunWorkerCompleted;
void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
//DU STUFF HERE
}
void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
/DO STUFF HERE LIKE HIDE / SHOW / ENABLE/ DISABLE buttons
}
I'm beginner in C#. And i have problem with threads when i using win.forms. My application freezes. What the problem with this code? I'm using microsoft example from msdn.
Here's my code:
delegate void SetTextCallback(object text);
private void WriteString(object text)
{
// 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.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(WriteString);
this.Invoke(d, new object[] { text });
}
else
{
for (int i = 0; i <= 1000; i++)
{
this.textBox1.Text = text.ToString();
}
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread th_1 = new Thread(WriteString);
Thread th_2 = new Thread(WriteString);
Thread th_3 = new Thread(WriteString);
Thread th_4 = new Thread(WriteString);
th_1.Priority = ThreadPriority.Highest; // самый высокий
th_2.Priority = ThreadPriority.BelowNormal; // выше среднего
th_3.Priority = ThreadPriority.Normal; // средний
th_4.Priority = ThreadPriority.Lowest; // низкий
th_1.Start("1");
th_2.Start("2");
th_3.Start("3");
th_4.Start("4");
th_1.Join();
th_2.Join();
th_3.Join();
th_4.Join();
}
There is a deadlock - UI thread is waiting for threads to complete with Thread.Join() while the worker threads are trying to send a message to UI using blocking Control.Invoke(). Replacing the Invoke in the thread code by BeginInvoke() will make the deadlock go away
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(WriteString);
// BeginInvoke posts message to UI thread asyncronously
this.BeginInvoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text.ToString();
}
It freezes because of the Join calls. Thread.Join() makes the current thread wait after another one is complete.
I want to get alive or dead ip addresses from a big lan. but it takes so many times. I decided to use backgroundworker here is my code:
try
{
this.Status.Text = "Collecting Information...";
if(this.TxtWorkGroup.Text.Trim() == "")
{
MessageBox.Show("The Work Group name Should Not be Empty");
return;
}
// Use Your work Group WinNT://&&&&(Work Group Name)
DirectoryEntry DomainEntry = new DirectoryEntry("WinNT://" + this.TxtWorkGroup.Text.Trim());
DomainEntry.Children.SchemaFilter.Add("computer");
// To Get all the System names And Display with the Ip Address
foreach(DirectoryEntry machine in DomainEntry.Children)
{
string[] Ipaddr = new string[3];
Ipaddr[0] = machine.Name;
System.Net.IPHostEntry Tempaddr = null;
try
{
Tempaddr = (System.Net.IPHostEntry)Dns.GetHostByName(machine.Name);
}
catch(Exception)
{
//MessageBox.Show("Unable to connect woth the system :" + machine.Name );
deadHostList.Items.Add(machine.Name);
continue;
}
System.Net.IPAddress[] TempAd = Tempaddr.AddressList;
foreach(IPAddress TempA in TempAd)
{
Ipaddr[1] = TempA.ToString();
byte[] ab = new byte[6];
int len = ab.Length;
// This Function Used to Get The Physical Address
int r = SendARP( (int) TempA.Address, 0, ab, ref len );
string mac = BitConverter.ToString( ab, 0, 6 );
Ipaddr[2] = mac;
}
System.Windows.Forms.ListViewItem TempItem = new ListViewItem(Ipaddr);
this.ListHostIP.Items.Add(TempItem);
}
this.Status.Text = "Displayed";
}
catch(Exception ex)
{
MessageBox.Show(ex.Message,"Error",System.Windows.Forms.MessageBoxButtons.OK );
Application.Exit();
}
but when I try to use these codes in backgroundWorker1_DoWork event it gives me error messages
Cross-thread operation not valid: Control 'deadHostList' accessed from a thread other than the thread it was created on
How can I modify my codes?
As Chris and Reed stated....you can only modify a Ui control from the thread that the control was created on...
You can also use the BackgroundWorker's ProgressChanged event for Ui updates....
var worker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
/// runs on background thread
worker.DoWork += (s, e) =>
{
while (!done)
{
DoSomeWork();
// send a message to the Ui
// via the ProgressChanged event
worker.ReportProgress(percent, statusMessage);
}
};
/// the ProgressChanged event runs on the UI thread
worker.ProgressChanged += (s, e) =>
{
var msg = (MyStatusMessage)e.UserState;
someUiControl.Items.Add(msg.Text);
};
/// also runs on Ui thread
worker.RunWorkerCompleted += (s, e) =>
{
};
worker.RunWorkerAsync();
You cannot and should not access UI controls from any thread other than the thread that created the control. I guess your deadHostList is a ListView control or something similar.
You can marshal a request from the background thread to the UI thread by using Control.Invoke or Control.BeginInvoke
You need to always marshal calls to UI elements, such as your list control, onto the UI thread.
You can do this via Control.Invoke. Change this:
deadHostList.Items.Add(machine.Name);
To:
string name = machine.Name;
deadHostList.Invoke(new Action( () => deadHostList.Items.Add(name)));
You'll also need to do the same thing later for ListHostIP - make sure to use Control.Invoke to wrap that call as well.