Changing TextBox Value when task completed - c#

I have a WinForms application that has two forms. On the first form the user enters some information and clicks the next button, this hides form1, loads form2 and fires a task (the function of which is on form1) that loads data from the database.
What I would like to do is on the second form have a text box that displays "Loading Data" whilst the task is running and then the count of the rows returned when the task has complete. All the while allowing the user to continue to enter data on form2.
This is the first time I have tried using tasks and async and I'm struggling to figure out a way to do this.
Task emailTask = new Task(() => FindCustomersForEmail(reg.Index));
emailTask.Start();
MessageControl formMessageControl = new MessageControl();
formMessageControl.Show();
this.Hide();
Whats in my head is something like:
while (emailTask not complete)
{
txtEmailCount.Text = "Loading";
}
txtEmailCount.Text = customersToEmail.Rows.Count.ToString();

Why not just await on the Task?
// Note this is void because I'm assuming it's an event handler.
// If it isn't this should be `async Task` instead.
public async void FindCustomersAsync()
{
txtEmailCount.Text = "Loading";
await Task.Run(() => FindCustomersForEmail(reg.Index));
txtEmailCount.Text = customersToEmail.Rows.Count.ToString();
}
Side note - prefer using Task.Run instead of manually starting the Task.

Related

Form is displaying before async function completes

I've got a WinForms project that scans a given network and returns valid IP addresses. Once all the addresses are found, I create a user control for each and place it on the form. My functions to ping ip addresses use async and Task which I thought would "wait" to execute before doing something else, but it doesn't. My form shows up blank, then within 5 seconds, all the user controls appear on the form.
Declarations:
private List<string> networkComputers = new List<string>();
Here's the Form_Load event:
private async void MainForm_Load(object sender, EventArgs e)
{
//Load network computers.
await LoadNetworkComputers();
LoadWidgets();
}
The LoadNetworkComputers function is here:
private async Task LoadNetworkComputers()
{
try
{
if (SplashScreenManager.Default == null)
{
SplashScreenManager.ShowForm(this, typeof(LoadingForm), false, true, false);
SplashScreenManager.Default.SetWaitFormCaption("Finding computers");
}
else
Utilities.SetSplashFormText(SplashForm.SplashScreenCommand.SetLabel, "Scanning network for computers. This may take several minutes...");
networkComputers = await GetNetworkComputers();
}
catch (Exception e)
{
MessageBox.Show(e.Message + Environment.NewLine + e.InnerException);
}
finally
{
//Close "loading" window.
SplashScreenManager.CloseForm(false);
}
}
And the last 2 functions:
private async Task<List<string>> GetNetworkComputers()
{
networkComputers.Clear();
List<string> ipAddresses = new List<string>();
List<string> computersFound = new List<string>();
for (int i = StartIPRange; i <= EndIPRange; i++)
ipAddresses.Add(IPBase + i.ToString());
List<PingReply> replies = await PingAsync(ipAddresses);
foreach(var reply in replies)
{
if (reply.Status == IPStatus.Success)
computersFound.Add(reply.Address.ToString());
}
return computersFound;
}
private async Task<List<PingReply>> PingAsync(List<string> theListOfIPs)
{
var tasks = theListOfIPs.Select(ip => new Ping().SendPingAsync(ip, 2000));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
I'm really stuck on why the form is being displayed before the code in the MainForm_Load event finishes.
EDIT
I forgot to mention that in the LoadNetworkComputers it loads a splash form which lets the user know that the app is running. It's when the form shows up behind that, that I'm trying to avoid. Here's a screenshot (sensitive info has been blacked out):
The reason one would use async-await is to enable callers of functions to continue executing code whenever your function has to wait for something.
The nice thing is that this will keep your UI responsive, even if the awaitable function is not finished. For instance if you would have a button that would LoadNetworkComputers and LoadWidgets you would be glad that during this relatively long action your window would still be repainted.
Since you've defined your Mainform_Loadas async, you've expressed that you want your UI to continue without waiting for the result of LoadNetWorkComputers.
In this interview with Eric Lippert (search in the middle for async-await) async-await is compared with a a cook making dinner. Whenever the cook finds that he has to wait for the bread to toast, he starts looking around to see if he can do something else, and starts doing it. After a while when the bread is toasted he continues preparing the toasted bread.
By keeping the form-load async, your form is able to show itself, and even show an indication that the network computers are being loaded.
An even nicer method would be to create a simple startup-dialog that informs the operator that the program is busy loading network computers. The async form-load of this startup-dialog could do the action and close the form when finished.
public class MyStartupForm
{
public List<string> LoadedNetworkComputers {get; private set;}
private async OnFormLoad()
{
// start doing the things async.
// keep the UI responsive so it can inform the operator
var taskLoadComputers = LoadNetworkComputers();
var taskLoadWidgets = LoadWidgets();
// while loading the Computers and Widgets: inform the operator
// what the program is doing:
this.InformOperator();
// Now I have nothing to do, so let's await for both tasks to complete
await Task.WhenAll(new Task[] {taskLoadComputers, taskLoadWidgets});
// remember the result of loading the network computers:
this.LoadedNetworkComputers = taskLoadComputers.Result;
// Close myself; my creator will continue:
this.Close();
}
}
And your main form:
private void MainForm_Load(object sender, EventArgs e)
{
// show the startup form to load the network computers and the widgets
// while loading the operator is informed
// the form closes itself when done
using (var form = new MyStartupForm())
{
form.ShowDialog(this);
// fetch the loadedNetworkComputers from the form
var loadedNetworkComputers = form.LoadedNetworkComputers;
this.Process(loadedNetworkComputers);
}
}
Now while loading, instead of your mainform the StartupForm is shown while the items are loaded.. The operator is informed why the main form is not showing yet. As soon as loading is finished, the StartupForm closes itself and loading of the main form continues
My form shows up blank, then within 5 seconds, all the user controls appear on the form.
This is by design. When the UI framework asks your app to display a form, it must do so immediately.
To resolve this, you'll need to decide what you want your app to look like while the async work is going on, initialize to that state on startup, and then update the UI when the async work completes. Spinners and loading pages are a common choice.

Locked Controls during Special Process

I wrote a function that download something like a pic from net , and use it
in a click event of special button.
my problem is when i click on button and the app start downloading pic from
internet , all of my controls in form , lock (until download process is done!)
not just only controls , all of my form too.
so how can i handle this process in background of application and the
user can work with other control at Same time.
All you need is asynchronous programming
here is a very simple demo
private async void button1_Click(object sender, EventArgs e)
{
this.Text = "doing something...";
var result = await SomeHeavyWork();
this.Text = result.ToString();
}
private async Task<int> SomeHeavyWork()
{
using (var hc = new HttpClient())
{
var data = await hc.GetAsync("www.google.com");
return data.Content.Headers.Count();
}
}
What's happening here?
when you click button1, button1_Click will execute.
it first set the form text to "doing something..."
it now waits for SomeHeavyWork() to complete its work.
we are now exiting button1_Click function and do what we were doing before clicking button1(running the form message loop). but somewhere else we execute SomeHeavyWork() and waiting for it.
when SomeHeavyWork() job is finished we came back to button1_Click and execute the this.Text = result.ToString(); line.
please read this msdn article

How to print on UI and then make UI Thread wait for sometime in the same Click event in Windows phone app

I have a case where, I need to print something in textbox1 then wait for a second, make an image visible and then again wait for a second then print something in textbox2 in one button click. When I wrote a sleep after the printing in textbox1 in the Click event of the button. I see that the printing on the UI is done all at a time, i.e. I expect it to be done sequentially one after another with a pause, but since its a single event handle it waits till end and show up on UI all at a time in the end.
I don't know if it work like for Windows phone 8 but you can "sleep" making the method async and calling
await Task.Delay(1000);
public async void myMethod(){
printString1();
await Task.Delay(1000);
showImage();
await Task.Delay(1000);
printString2();
}
You can put your code in a thread, and use the dispatcher when you need to update the UI:
private void Process()
{
this.Dispatcher.BeginInvoke(() => this.TextBox1.Text = "Hello");
Thread.Sleep(1000);
this.Dispatcher.BeginInvoke(() => this.Picture1.Visibility = Visibility.Visible);
Thread.Sleep(1000);
this.Dispatcher.BeginInvoke(() => this.TextBox2.Text = "World");
}
Then, to start the thread (when the user clicks on the button):
var thread = new Thread(this.Process);
thread.Start();

Why do I get an InvalidOperationException (cross-thread operation not valid) when processing ribbon click events?

I've created a "progress/cancel" mechanism for my application whereby I can show a modal dialog while executing long-running operations, and have the dialog give some indication of progress. The dialog also has a cancel button to allow the user to cancel the operation. (Thanks to the SO community for helping me with this part).
Here's what the code looks like for running a dummy long-running operation:
public static async Task ExecuteDummyLongOperation()
{
await ExecuteWithProgressAsync(async (ct, ip) =>
{
ip.Report("Hello world!");
await TaskEx.Delay(3000);
ip.Report("Goodbye cruel world!");
await TaskEx.Delay(1000);
});
}
The parameters to the lamba are a CancellationToken and an IProgress. I'm not using the CancellationToken in this example, but the IProgress.Report method is setting the text for a label control on my progress/cancel form.
If I start this long-running operation from the button click handler on a form, it works fine. However, I've now discovered that if I start the operation from the click event handler for a ribbon button in a VSTO PowerPoint add-in, it fails at the second call to ip.Report (at the point where it tries to set the text of the label control). In this case, I get the dreaded InvalidOperationException saying that there's an invalid cross-thread operation.
There are two things that I find puzzling:
Why does the problem occur when the operation is invoked by clicking a button on the ribbon but not when invoked by clicking a button on a form?
Why does the problem occur at the second call to ip.Report but not at the first? I've not switched threads between those two calls.
You will of course want to see the rest of the code. I've tried to strip everything back to the bare bones:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncTestPowerPointAddIn
{
internal partial class ProgressForm : Form
{
public ProgressForm()
{
InitializeComponent();
}
public string Progress
{
set
{
this.ProgressLabel.Text = value;
}
}
private void CancelXButton_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
public static async Task ExecuteDummyLongOperation()
{
await ExecuteWithProgressAsync(async (ct, ip) =>
{
ip.Report("Hello world!");
await TaskEx.Delay(3000);
ip.Report("Goodbye cruel world!");
await TaskEx.Delay(1000);
});
}
private static async Task ExecuteWithProgressAsync(Func<CancellationToken, IProgress<string>, Task> operation)
{
var cancellationTokenSource = new CancellationTokenSource();
var progress = new Progress<string>();
var operationTask = operation(cancellationTokenSource.Token, progress);
// Don't show the dialog unless the operation takes more than a second
const int TimeDelayMilliseconds = 1000;
var completedTask = TaskEx.WhenAny(TaskEx.Delay(TimeDelayMilliseconds), operationTask).Result;
if (completedTask == operationTask)
await operationTask;
// Show a progress form and have it automatically close when the task completes
using (var progressForm = new ProgressForm())
{
operationTask.ContinueWith(_ => { try { progressForm.Close(); } catch { } }, TaskScheduler.FromCurrentSynchronizationContext());
progress.ProgressChanged += ((o, s) => progressForm.Progress = s);
if (progressForm.ShowDialog() == DialogResult.Cancel)
cancellationTokenSource.Cancel();
}
await operationTask;
}
}
}
The form itself simply has a label (ProgressLabel) and a button (CancelXButton).
The button click event handlers for both the ribbon button and the form button simply call the ExecuteDummyLongOperation method.
EDIT: More Information
At #JamesManning's request, I put in some tracing to watch the value of the ManagedThreadId, as follows:
await ExecuteWithProgressAsync(async (ct, ip) =>
{
System.Diagnostics.Trace.TraceInformation("A:" + Thread.CurrentThread.ManagedThreadId.ToString());
ip.Report("Hello world!");
System.Diagnostics.Trace.TraceInformation("B:" + Thread.CurrentThread.ManagedThreadId.ToString());
await TaskEx.Delay(3000);
System.Diagnostics.Trace.TraceInformation("C:" + Thread.CurrentThread.ManagedThreadId.ToString());
ip.Report("Goodbye cruel world!");
System.Diagnostics.Trace.TraceInformation("D:" + Thread.CurrentThread.ManagedThreadId.ToString());
await TaskEx.Delay(1000);
System.Diagnostics.Trace.TraceInformation("E:" + Thread.CurrentThread.ManagedThreadId.ToString());
});
This was interesting. When invoked from the form, the thread ID does not change. However, when invoked from the ribbon, I get:
powerpnt.exe Information: 0 : A:1
powerpnt.exe Information: 0 : B:1
powerpnt.exe Information: 0 : C:8
powerpnt.exe Information: 0 : D:8
So, the thread ID is changing when we 'return' from that first await.
I'm also surprised that we see "D" in the trace at all, since the call immediately prior to that is where the exception occurs!
This is the expected outcome if the current thread, the one that you called ExecuteDummyLongOperation() on, doesn't have a synchronization provider. Without one, the continuation after the await operator can only run on a threadpool thread.
You can diagnose this by putting a breakpoint on the await expression. Inspect the value of System.Threading.SynchronizationContext.Current. If it is null then there is no synchronization provider and your code will fail as expected when you update the form from the wrong thread.
It isn't entirely clear to me why you don't have one. You get a provider by creating a form on the thread, before calling the method. That automatically installs a provider, an instance of the WindowsFormsSynchronizationContext class. Looks to me like you create your ProgressForm too late.

Closing dialogs from another threads

I'm having a problem with threads. My code:
Task.Factory.StartNew(() =>
{
cts = new CancellationTokenSource();
var lines = File.ReadLines(Path.Combine(Environment.CurrentDirectory, "urls.txt"));
try
{
var q = from line in lines.AsParallel().WithDegreeOfParallelism(30).WithCancellation(cts.Token)
let result = Parse(line, cts.Token)
select new
{
res = result
};
foreach (var x in q)
{
if (x != null)
{
Console.WriteLine("{0}", x.res);
}
}
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
});
Now in Parse I have:
public String Parse(String url,CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
/* many lines of code */
InputForm iForm = new InputForm();
iForm.setPageData(pageData);
if (iForm.ShowDialog() == DialogResult.OK)
{
string userInput = iForm.textBox.Text;
/* code block */
return result;
} else {
return Parse(newUrl,ct);
}
}
I'm using ShowDialog because I need to get user input from iForm (this form has a timer and is auto closed after 60 seconds). Now, when I opened about 30 forms and click Cancel (on main form) this dialog forms need to be closed manualy. Is it posible to close this form after clicking Cancel?
I do this a lot.
What you're going to need to do is
create a way to communicate with your Main Thread Of Execution (MTOE)
call your main thread and wait for the response
in your main thread, display your dialog box
set the return value for your thread
signal your thread that you are done
A custom event handler works great for getting a message from your thread back to the MTOE.
A ManualResetEvent is good for your thread to know when the MTOE is complete.
A class instance can be passed in an event handler that the MTOE uses to fill a few data items and pass back to the thread whenever it is done.
Typically, when I create my special class, it contains the event handler and the ManualResetEvent object.
From your MTOE, if you close your form, you can signal all of your waiting dialog boxes to Cancel.
This would require a little redesign, but I think it would give you what you are after.
You may want to look at http://msdn.microsoft.com/en-us/library/system.windows.forms.application.openforms.aspx
You could iterate over the open open forms and call close on those that are of type InputForm
EDIT:
The below comment is correct this would throw an exception. You would actually need something like FormToClose.BeginInvoke(delegate ()=> FormToClose.Close());

Categories