Using await Task inside backgroundWorker is changing my status to complete - c#

I am using a loop each 7 seconds. This loop communicates with a server. Then when server is answering i am waiting user to answer about servers response. So i need to check if all this process has finished.
So i am creating a timer
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (sender, e) => backgroundWorker_DoWork(sender, e, frm);
var timer = new System.Timers.Timer();
timer.Enabled = true;
timer.Interval = 10000;
timer.Elapsed += (sender, e) => Timer_Elapsed(sender, e);
timer.Start();
private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (!backgroundWorker.IsBusy)
{
backgroundWorker.RunWorkerAsync();
}
}
private static async void backgroundWorker_DoWork(object sender, DoWorkEventArgs e, Main frm)
{
using (var client = new HttpClient())
{
var result = await client.GetAsync(uri);
//message to user for answering, then return it to server again
}
}
For some reason when i am using await..., 'backgroundWorker.IsBusy' flag turns to false. I mean that i can't have correct result if task has actually finished. Should i use a flag for this? or there is more efficient way?

You can't use await inside a BackgroundWorker's DoWork. I recommend replacing BackgroundWoker completely with Task.Run:
Task task = null;
var timer = new Timer();
timer.Interval = 10000;
timer.Tick += Timer_Tick;
timer.Start();
private static async void Timer_Tick(object sender, EventArgs e)
{
if (task == null)
{
task = Task.Run(() => DoWorkAsync(frm));
try { await task; }
finally { task = null; }
}
}
private static async Task DoWorkAsync(Main frm)
{
using (var client = new HttpClient())
{
var result = await client.GetAsync(uri);
//message to user for answering, then return it to server again
}
}
Side note: I changed System.Timers.Timer to System.Windows.Forms.Timer so that the task variable is always accessed from the UI thread.

Related

async/await continuing too soon

So I'm basically trying to delay the invocation of filter process by 1.5 seconds to allow user to type multiple keystrokes in case they want to. If a new keystroke is typed, previously waiting task is cancelled and a new one starts waiting:
System.Threading.CancellationTokenSource token = new System.Threading.CancellationTokenSource();
private async void MyTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
token.Cancel();
await System.Threading.Tasks.Task.Delay(1500, token.Token);
this.filterText = (sender as TextBox).Text;
(this.Resources["CVS"] as CollectionViewSource).View.Refresh();
//Earlier I had tried this variant too:
//System.Threading.Tasks.Task.Delay(500, token.Token).ContinueWith(_ =>
//{
// this.filterText = (sender as TextBox).Text;
// (this.Resources["CVS"] as CollectionViewSource).View.Refresh();
//});
}
But the filter process (View.Refresh() line) hits immediately on first keystroke without waiting. My impression was that calling Cancel on the token would kill Delay() and thereby the continuation task too, before planting the next one, but apparently this scheme doesn't work.
What am I missing?
The proper way to handle this is not with Task.Delay and exceptions (as exceptions are for exceptional circumstances), but using a Timer with the Timer.Elapsed event.
E.g.
using Timer = System.Windows.Forms.Timer;
private readonly Timer timer = new Timer();
private static string newText = "";
public Form1()
{
timer.Interval = 1500;
timer.Tick += OnTimedEvent;
}
private void MyTextBox_TextChanged(object sender, EventArgs e)
{
timer.Stop(); // sets the time back to 0
newText = (sender as TextBox).Text; // sets new text
timer.Start(); // restarts the timer
}
private void OnTimedEvent(Object source, EventArgs e)
{
filterText = newText;
(Resources["CVS"] as CollectionViewSource).View.Refresh();
}
(Not sure this is 100% correct, but you get the gist.)
Old snippet relating to the discussions in the comments.
As the post says: this is not needed, as Task.Delay will link a listener to the CancellationToken, thus .Cancel() will block until all listeners have heard it.
using System.Threading;
using System.Threading.Tasks;
private CancellationTokenSource cts = new CancellationTokenSource();
private Task delayTask;
private async void TenantsFilter_TextChanged(object sender, TextChangedEventArgs e)
{
cts.Cancel();
if (delayTask != null) {
try{await delayTask;}
catch(TaskCanceledException){}
}
cts = new CancellationTokenSource();
try
{
delayTask = Task.Delay(1500, cts.Token);
await delayTask;
this.filterText = (sender as TextBox).Text;
(this.Resources["CVS"] as CollectionViewSource).View.Refresh();
}
catch(TaskCanceledException)
{
}
}
If this helps anyone, the following is correctly working for me. My mistake was that I incorrectly assumed that CancellationTokenSource is a signaling device and could be used multiple times. That is apparently not the case:
private System.Threading.CancellationTokenSource cts = new System.Threading.CancellationTokenSource();
private async void TenantsFilter_TextChanged(object sender, TextChangedEventArgs e)
{
cts.Cancel();
cts = new System.Threading.CancellationTokenSource();
try
{
await System.Threading.Tasks.Task.Delay(1500, cts.Token);
this.filterText = (sender as TextBox).Text;
(this.Resources["CVS"] as CollectionViewSource).View.Refresh();
}
catch(System.Threading.Tasks.TaskCanceledException ee)
{
}
}
Posting it here for my own record and just to let others check I'm still not doing anything wrong.

Background task in UWP

I want to use a background task for my UWP app.
The below code, is my back button click event in windows mobile:
private async void MainPage_BackRequested(object sender, BackRequestedEventArgs e)
{
var access= await BackgroundExecutionManager.RequestAccessAsync();
var task = new BackgroundTaskBuilder
{
Name="My task",TaskEntryPoint=typeof(backGroundTask.Class1).ToString()
};
trigger = new ApplicationTrigger();
task.SetTrigger(trigger);
task.Register();
//var result = await trigger.RequestAsync();
if (Frame.CanGoBack)
{
Frame.GoBack();
e.Handled = true;
}
}
public void Run(IBackgroundTaskInstance taskInstance)
{
_deferral = taskInstance.GetDeferral();
clearData();
count1 = 0;
getDownloadedSongs();
dispatcherTimer1.Tick += DispatcherTimer1_Tick;
dispatcherTimer1.Interval = new TimeSpan(0, 0, 3);
dispatcherTimer1.Start();
_deferral.Complete();
}
DispatcherTimer dispatcherTimer1 = new DispatcherTimer();
private async void DispatcherTimer1_Tick(object sender, object e)
{
try
{
clearData();
}
catch (Exception ex)
{
}
}
But this code is not working, when I click back button. As per expectation background task code should work, but it is not working. What am I doing wrong?
Your background task is exiting before the DispatcherTimer gets a chance to ever execute, because you mark the Deferral as complete. You should hold on to the Deferral until all the work in your background task has been completed (or until you receive a TaskCanceled event from the system).

Multi threading approach

I have developed the below code using TPL and all works fine except how to restart the task. Currently when I click button1 the thread starts in textBox1 and similarly with button3 and textBox2. When I click button3 and button4 both the threads get's cancelled.
Now I want to restart the thread where left off on the click event of button1 and button3. I tried many approaches but could not get any idea on how to do this.
Here's the code:
public partial class Form1 : Form
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token;
CancellationTokenSource cancellationTokenSource1 = new CancellationTokenSource();
CancellationToken token1;
public Form1()
{
InitializeComponent();
token = cancellationTokenSource.Token;
token1 = cancellationTokenSource1.Token;
}
private void button1_Click(object sender, EventArgs e)
{
Task t = Task.Factory.StartNew(() =>
{
var run = true;
while (run)
{
for (int i = 0; i < 100; i++)
{
if (token.IsCancellationRequested)
{
run = false;
break;
//token.ThrowIfCancellationRequested();
}
// your code
System.Threading.Thread.Sleep(1000);
Action act = () => textBox1.Text = Convert.ToString(i);
textBox1.Invoke(act);
}
}
});
}
private void button2_Click(object sender, EventArgs e)
{
cancellationTokenSource.Cancel();
}
private void button3_Click(object sender, EventArgs e)
{
Task t1 = Task.Factory.StartNew(() =>
{
var run = true;
while (run)
{
for (int i = 0; i < 100; i++)
{
if (token1.IsCancellationRequested)
{
run = false;
break;
//token1.ThrowIfCancellationRequested();
}
// your code
System.Threading.Thread.Sleep(1000);
Action act = () => textBox2.Text = Convert.ToString(i);
textBox2.Invoke(act);
}
}
});
}
private void button4_Click(object sender, EventArgs e)
{
cancellationTokenSource1.Cancel();
}
}
You need to sync thread using signaling, generally they are achieved using Event Wait Handles like AutoResetEvent, ManualReset and CountdownEvent. You can achieve this with ManualReset.
Declare ManualResetEvent:
CancellationTokenSource cancellationTokenSource1 = new CancellationTokenSource();
CancellationToken token1;
private ManualResetEvent mre = new ManualResetEvent(false);
Modify on CancellationRequest
if (token.IsCancellationRequested) {
run = false;
mre.WaitOne();
//token.ThrowIfCancellationRequested();
}
OnStart/OnResume: mre.Set();

Understanding cross-threading Controls access C#

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.

Timer methods not called

I have setup a timer:
System.Timers.Timer timer = new System.Timers.Timer (1000);
timer.Enabled = true;
timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) => {
timer.Enabled = false;
ui.CldOnPhoneNumberSent(); // <- this method is not fired
};
the second method is not called.
if i switch the methods as in:
timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) => {
ui.CldOnPhoneNumberSent();
timer.Enabled = false; // <- this method is not called and the timer repeats
}
what's wrong?
Edit:
When the method is called from a timer, it's not called completely!:
timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) => {
((Timer)sender).Enabled = false;
ui.method1();
};
void method1()
{
do something; //<-- called
do Something; //<-- not called
}
It could be a problem with variable closure in the anonymous method - try using the sender value instead of referencing timer:
timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) => {
((Timer)sender).Enabled = false;
ui.CldOnPhoneNumberSent(); // <- this method is not fired
};
As was said in comments, the most likely reason is that your CldOnPhoneNumberSent() throws some exception preventing further execution.
You should rewrite as follow:
var timer = new System.Timers.Timer (1000);
timer.Elapsed += (sender, args) =>
{
((Timer)sender).Enabled = false;
try
{
ui.CldOnPhoneNumberSent();
}
catch (Exception e)
{
// log exception
// do something with it, eventually rethrow it
}
};
timer.Enabled = true;
Note that is you are inside a WPF application and want to access object created in the UI thread, you may need to dispatch the call:
Action callback = ui.CldOnPhoneNumberSent;
var app = Application.Current;
if (app == null)
{
// This prevents unexpected exceptions being thrown during shutdown (or domain unloading).
return;
}
if (app.CheckAccess())
{
// Already on the correct thread, just execute the action
callback();
}
else
{
// Invoke through the dispatcher
app.Dispatcher.Invoke(callback);
}
As a final note, if you are using .Net 4.5 (with C# 5) you might consider using the async/await pattern instead of System.Timers.Timer, which is easier to use and more readable:
private async Task YourMethod()
{
await Task.Delay(1000)
.ConfigureAwait(continueOnCapturedContext: true); // this makes sure continuation is on the same thread (in your case it should be the UI thread)
try
{
ui.CldOnPhoneNumberSent();
}
catch (Exception e)
{
// log exception
// do something with it, eventually rethrow it
}
}

Categories

Resources