How to do progress reporting using Async/Await - c#

suppose i have a list of files which i have to copy to web server using ftp related classes in c# project. here i want to use Async/Await feature and also want to show multiple progress bar for multiple file uploading at same time. each progress bar indicate each file upload status. so guide me how can i do this.
when we work with background worker to do this kind of job then it is very easy because background worker has progress change event. so how to handle this kind of situation with Async/Await. if possible guide me with sample code. thanks

Example code with progress from the article
public async Task<int> UploadPicturesAsync(List<Image> imageList,
IProgress<int> progress)
{
int totalCount = imageList.Count;
int processCount = await Task.Run<int>(() =>
{
int tempCount = 0;
foreach (var image in imageList)
{
//await the processing and uploading logic here
int processed = await UploadAndProcessAsync(image);
if (progress != null)
{
progress.Report((tempCount * 100 / totalCount));
}
tempCount++;
}
return tempCount;
});
return processCount;
}
private async void Start_Button_Click(object sender, RoutedEventArgs e)
{
int uploads=await UploadPicturesAsync(GenerateTestImages(),
new Progress<int>(percent => progressBar1.Value = percent));
}
If you want to report on each file independently you will have different base type for IProgress:
public Task UploadPicturesAsync(List<Image> imageList,
IProgress<int[]> progress)
{
int totalCount = imageList.Count;
var progressCount = Enumerable.Repeat(0, totalCount).ToArray();
return Task.WhenAll( imageList.map( (image, index) =>
UploadAndProcessAsync(image, (percent) => {
progressCount[index] = percent;
progress?.Report(progressCount);
});
));
}
private async void Start_Button_Click(object sender, RoutedEventArgs e)
{
int uploads=await UploadPicturesAsync(GenerateTestImages(),
new Progress<int[]>(percents => ... do something ...));
}

Related

For loop only runs once with ExecuteScriptAsync method

Environment is C# and WinForms. I am trying to run a program that will create an image download element in an already created website. I am using WebView2 as the browser. I changed the for-loop to 2 iterations just to debug this issue. I can download 1 image successfully but my result is maxed out at 1 iteration. Thanks for any help! Below is the code giving me issues:
async void multiplePics (int column) => await webView2.ExecuteScriptAsync("" +
"var downloadElement=document.createElement('a'); " +
"downloadElement.setAttribute('download',''); " +
"downloadElement.href= document.getElementsByClassName('slick-slide slick-cloned')[" + column + "].getElementsByClassName('item')[0].getAttribute('href'); " +
"document.body.appendChild(downloadElement); " +
"downloadElement.click();" +
"downloadElement.remove(); " +
"");
for (int i = 0; i <= 1; i++)
{
Debug.WriteLine(i);
multiplePics( i);
}
have tried:
async private void button5_Click(object sender, EventArgs e)
{
void multiplePics(int column) {
//webView2.ExecuteScriptAsync( "javascript");
}
for (int i = 0; i <= 1; i++)
{await multiplePics(i);}
}
have also tried:
private void button5_Click(object sender, EventArgs e)
{
Task<string> multiplePics(int column) {
//return webView2.ExecuteScriptAsync( "javascript");
}
Task.Run( ()=>{ return multiplePics(0);} );
Task.Run( ()=>{ return multiplePics(1);} );
//tried GetAwaiter() along with GetResult() also
}
another attempt:
private async void button5_Click(object sender, EventArgs e)
{
//tried public & private async Task multiplePics with no success
//async Task multiplePics had no errors but had the same result
private async Task multiplePics(int column) =>
await webView2.ExecuteScriptAsync("" +
"var downloadElement=document.createElement('a'); " +
"downloadElement.setAttribute('download',''); " +
"downloadElement.href= document.getElementsByClassName('slick-slide slick-cloned')[" + column + "].getElementsByClassName('item')[0].getAttribute('href'); " +
"document.body.appendChild(downloadElement); " +
"downloadElement.click();" +
"downloadElement.remove(); " +
"");
for (int i = 0; i <= 3; i++)
{
await multiplePics(i);
}
}
First thing is to update the signature of multiplePics to return a Task:
private async Task multiplePics (int column) => await webView2.ExecuteScriptAsync(...);
Then, you can use your method from your event handler by including async in the signature:
// event handlers use async void, not async Task
private async void button5_Click(object sender, EventArgs e)
Finally, you can now use your multiplePics method in the event handler:
private async void button5_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 1; i++)
{
await multiplePics(i);
}
}
However, given the above loop is only defined to iterate once twice, update the number of loops; let's say 3 for now:
private async void button5_Click(object sender, EventArgs e)
{
for (int i = 0; i < 3; i++) // 3 loops
{
await multiplePics(i);
}
}
Assuming the above will now download 3 images serially, you'll eventually want to download them in parallel and without blocking the UI. Personally, I would recommend using a BackgroundWorker, but that's an entirely different question.
Finally, if the above still is not working, you'll need to provide more information as to what this means: "but my result is maxed out at 1 iteration".
Edit
For more information on using async/await, I'd suggest you start with reviewing at least these posts/articles which go into some detail about when to return a Task and when void is acceptable (hint, only with events).
SO answer written by one of the C# language designers: What's the difference between returning void and returning a Task?
MSDN article written by one of the most knowledgeable devs on async/await: Async/Await - Best Practices in Asynchronous Programming
And the same author of the MSDN article answering an SO question: Why exactly is void async bad?
There are several other articles and SO q/a's that will go into even more depth on the topic; a bit of search on the relevant keywords will go a long way, ex: "async void vs async task"
Edit #2
Per my comments, use the following code that was taken directly from your latest sample, but adds a debug write for the result.
private async void button5_Click(object sender, EventArgs e)
{
for (int i = 0; i < 3; i++)
{
await multiplePics(i);
}
}
private async Task multiplePics(int column)
{
var result = await webView2.ExecuteScriptAsync("" +
"var downloadElement=document.createElement('a'); " +
"downloadElement.setAttribute('download',''); " +
"downloadElement.href=document.getElementsByClassName('slick-slide slick-cloned')[" + column + "].getElementsByClassName('item')[0].getAttribute('href'); " +
"document.body.appendChild(downloadElement); " +
"downloadElement.click();" +
"downloadElement.remove();" +
"");
Debug.WriteLine($"Col {column} result: {result}");
}
Dispite #Daniel response is the correct one, you can always do the following if there are indeed multiple donwloads you wish to do:
Create a list of task of boolean;
List<Task<bool>> lstImageTasks = new List<Task<bool>>();
Change you void "multiplePics" to become a bool function and then use this:
for (int i = 0; i <= 1; i++)
{
lstImageTasks.Add(multiplePics(i));
}
while (lstImageTasks.Any())
{
Task<bol> task = await Task.WhenAny(lstImageTasks);
var resTask = task.Result;
if (resTask != null)
{
if (resTask)
{
//SUCESS! do whatever you want... (implement log, Console.WriteLine(), ...)
}
else
{
//UPS...! do whatever you want... (implement log, Console.WriteLine(), ...)
}
}
lstFeedBackTasks.Remove(task);
}

Create a multi threaded applications to run multiple queries in c#

I'm trying to build a Windows Forms tool that runs queries asynchronously.
The app has a datagridview with 30 possible queries to run. The user checks the queries he wants to execute, say 10 queries, and hits a button.
The app has a variable called maxthreads = 3 (for the sake of discussion) that indicates how many threads can be used to async run the queries. The queries run on a production environment and we don't want to overload the system with too many threads running in the same time. Each query runs for an average of 30 sec. (some 5 min., others 2 sec.)
In the datagridview there is an image column containing an icon that depicts the status of each query (0- Available to be run, 1-Selected for running, 2- Running, 3- Successfully completed, -1 Error)
I need to be able to communicate with the UI every time a query starts and finishes. Once a query finishes, the results are being displayed in a datagridview contained in a Tabcontrol (one tab per query)
The approach: I was thinking to create a number of maxthread backgroundworkers and let them run the queries. As a backgroundworker finishes it communicates to the UI and is assigned to a new query and so on until all queries have been run.
I tried using an assignmentWorker that would dispatch the work to the background workers but don't know how to wait for all threads to finish. Once a bgw finishes it reports progress on the RunWorkerCompleted event to the assignmentWorker, but that one has already finished.
In the UI thread I call the assignment worker with all the queries that need to be run:
private void btnRunQueries_Click(object sender, EventArgs e)
{
if (AnyQueriesSelected())
{
tcResult.TabPages.Clear();
foreach (DataGridViewRow dgr in dgvQueries.Rows)
{
if (Convert.ToBoolean(dgr.Cells["chk"].Value))
{
Query q = new Query(dgr.Cells["ID"].Value.ToString(),
dgr.Cells["Name"].Value.ToString(),
dgr.Cells["FileName"].Value.ToString(),
dgr.Cells["ShortDescription"].Value.ToString(),
dgr.Cells["LongDescription"].Value.ToString(),
dgr.Cells["Level"].Value.ToString(),
dgr.Cells["Task"].Value.ToString(),
dgr.Cells["Importance"].Value.ToString(),
dgr.Cells["SkillSet"].Value.ToString(),
false,
new Dictionary<string, string>()
{ { "#ClntNb#", txtClntNum.Text }, { "#Staff#", "100300" } });
qryList.Add(q);
}
}
assignmentWorker.RunWorkerAsync(qryList);
}
else
{
MessageBox.Show("Please select at least one query.",
"Warning",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
Here is the AssignmentWorker:
private void assignmentWorker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (Query q in (List<Query>)e.Argument)
{
while (!q.Processed)
{
for (int threadNum = 0; threadNum < maxThreads; threadNum++)
{
if (!threadArray[threadNum].IsBusy)
{
threadArray[threadNum].RunWorkerAsync(q);
q.Processed = true;
assignmentWorker.ReportProgress(1, q);
break;
}
}
//If all threads are being used, sleep awhile before checking again
if (!q.Processed)
{
Thread.Sleep(500);
}
}
}
}
All bgw run the same event:
private void backgroundWorkerFiles_DoWork(object sender, DoWorkEventArgs e)
{
try
{
Query qry = (Query)e.Argument;
DataTable dtNew = DataAccess.RunQuery(qry).dtResult;
if (dsQryResults.Tables.Contains(dtNew.TableName))
{
dsQryResults.Tables.Remove(dtNew.TableName);
}
dsQryResults.Tables.Add(dtNew);
e.Result = qry;
}
catch (Exception ex)
{
}
}
Once the Query has returned and the DataTable has been added to the dataset:
private void backgroundWorkerFiles_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
try
{
if (e.Error != null)
{
assignmentWorker.ReportProgress(-1, e.Result);
}
else
{
assignmentWorker.ReportProgress(2, e.Result);
}
}
catch (Exception ex)
{
int o = 0;
}
}
The problem I have is that the assignment worker finishes before the bgw finish and the call to assignmentWorker.ReportProgress go to hell (excuse my French).
How can I wait for all the launched bgw to finish before finishing the assignment worker?
Thank you!
As noted in the comment above, you have overcomplicated your design. If you have a specific maximum number of tasks (queries) that should be executing concurrently, you can and should simply create that number of workers, and have them consume tasks from your queue (or list) of tasks until that queue is empty.
Lacking a good Minimal, Complete, and Verifiable code example that concisely and clearly illustrates your specific scenario, it's not feasible to provide code that would directly address your question. But, here's an example using a List<T> as your original code does, which will work as I describe above:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace TestSO42101517WaitAsyncTasks
{
class Program
{
static void Main(string[] args)
{
Random random = new Random();
int maxTasks = 30,
maxActive = 3,
maxDelayMs = 1000,
currentDelay = -1;
List<TimeSpan> taskDelays = new List<TimeSpan>(maxTasks);
for (int i = 0; i < maxTasks; i++)
{
taskDelays.Add(TimeSpan.FromMilliseconds(random.Next(maxDelayMs)));
}
Task[] tasks = new Task[maxActive];
object o = new object();
for (int i = 0; i < maxActive; i++)
{
int workerIndex = i;
tasks[i] = Task.Run(() =>
{
DelayConsumer(ref currentDelay, taskDelays, o, workerIndex);
});
}
Console.WriteLine("Waiting for consumer tasks");
Task.WaitAll(tasks);
Console.WriteLine("All consumer tasks completed");
}
private static void DelayConsumer(ref int currentDelay, List<TimeSpan> taskDelays, object o, int workerIndex)
{
Console.WriteLine($"worker #{workerIndex} starting");
while (true)
{
TimeSpan delay;
int delayIndex;
lock (o)
{
delayIndex = ++currentDelay;
if (delayIndex < taskDelays.Count)
{
delay = taskDelays[delayIndex];
}
else
{
Console.WriteLine($"worker #{workerIndex} exiting");
return;
}
}
Console.WriteLine($"worker #{workerIndex} sleeping for {delay.TotalMilliseconds} ms, task #{delayIndex}");
System.Threading.Thread.Sleep(delay);
}
}
}
}
In your case, each worker would report progress to some global state. You don't show the ReportProgress handler for your "assignment" worker, so I can't say specifically what this would look like. But presumably it would involve passing either -1 or 2 to some method that knows what to do with those values (i.e. what would otherwise have been your ReportProgress handler).
Note that the code can simplified somewhat, particularly where the individual tasks are consumed, if you use an actual queue data structure for the tasks. That approach would look something like this:
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace TestSO42101517WaitAsyncTasks
{
class Program
{
static void Main(string[] args)
{
Random random = new Random();
int maxTasks = 30,
maxActive = 3,
maxDelayMs = 1000,
currentDelay = -1;
ConcurrentQueue<TimeSpan> taskDelays = new ConcurrentQueue<TimeSpan>();
for (int i = 0; i < maxTasks; i++)
{
taskDelays.Enqueue(TimeSpan.FromMilliseconds(random.Next(maxDelayMs)));
}
Task[] tasks = new Task[maxActive];
for (int i = 0; i < maxActive; i++)
{
int workerIndex = i;
tasks[i] = Task.Run(() =>
{
DelayConsumer(ref currentDelay, taskDelays, workerIndex);
});
}
Console.WriteLine("Waiting for consumer tasks");
Task.WaitAll(tasks);
Console.WriteLine("All consumer tasks completed");
}
private static void DelayConsumer(ref int currentDelayIndex, ConcurrentQueue<TimeSpan> taskDelays, int workerIndex)
{
Console.WriteLine($"worker #{workerIndex} starting");
while (true)
{
TimeSpan delay;
if (!taskDelays.TryDequeue(out delay))
{
Console.WriteLine($"worker #{workerIndex} exiting");
return;
}
int delayIndex = System.Threading.Interlocked.Increment(ref currentDelayIndex);
Console.WriteLine($"worker #{workerIndex} sleeping for {delay.TotalMilliseconds} ms, task #{delayIndex}");
System.Threading.Thread.Sleep(delay);
}
}
}
}

WPF : Progress Bar update in Parallel.Foreach

I am trying to update the progress bar while running a Parallel.Foreach, but during execution nothing happens. Progressbar gets updated only when the For loop end. How can I make this code work?
XAML
<StackPanel>
<Grid x:Name="LoadProgressGrid" Height="100"
Visibility="Visible">
<ProgressBar x:Name="LoadProgress"
Maximum="100"
Minimum="1" />
<TextBlock Margin="0,0,0,-5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"><Run Text="Import Progress"/></TextBlock>
</Grid>
<Button Height="35" Width="100" Margin="0,10" Content="Test" Click="Test_Click"/>
</StackPanel>
C#
private void Test_Click(object sender, RoutedEventArgs e)
{
decimal current=0;
List<string> lst = new List<string>();
lst.Add("Foo");
lst.Add("Foo");
lst.Add("Foo");
lst.Add("Foo");
decimal max = 100000;
var uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
Parallel.ForEach(lst, (data) =>
{
for (int i = 0; i < max; i++)
{
// Use the uiFactory above:
// Note the need for a temporary here to avoid closure issues!
current = current + 1;
uiFactory.StartNew( () => LoadProgress.Value = (double)(current/max)*100);
}
});
MessageBox.Show("Done!");
}
As pointed out in the comments, Parallel.ForEach does not return until the loop has completed which will block the thread it runs on, and the comments by Servy in your answer also say that you are accessing and changing state from various threads without synchronization or object locking (see Race Conditions).
If you are unsure about the correct way to change state on the UI Thread then you can leave the framework do the work of capturing the context with IProgress<T>.
Regarding your answer, you can put the async keyword directly on the Test_Click event handler while keeping the return type void, but please bare in mind that void is not recommended or suggested for any other async methods and they should return a type of Task or Task<T>, read more here on why async void is a bad idea.
More to the point, here's a snippet making use of async and non-blocking code to report progress and update the progress bar, I have commented the code to be more readable.
// Add the async keyword to our event handler
private async void Button_Click(object sender, RoutedEventArgs e)
{
//Begin our Task
Task downloadTask = DownloadFile();
// Await the task
await downloadTask;
}
private async Task DownloadFile()
{
// Capture the UI context to update our ProgressBar on the UI thread
IProgress<int> progress = new Progress<int>(i => { LoadProgress.Value = i; });
// Run our loop
for (int i = 0; i < 100; i++)
{
int localClosure = i;
// Simulate work
await Task.Delay(1000);
// Report our progress
progress.Report((int)((double)localClosure / 100 * 100));
}
}
From this answer: Using Task with Parallel.Foreach in .NET 4.0 and Servy's comment I got it working.
private void Test_Click(object sender, RoutedEventArgs e)
{
test();
}
public async void test()
{
decimal current = 0;
List<string> lst = new List<string>();
lst.Add("Foo");
lst.Add("Foo");
lst.Add("Foo");
lst.Add("Foo");
decimal max = 10000;
//Remove await (and async from signature) if, want to see the message box rightway.
await Task.Run(() => Parallel.ForEach(lst, (data) =>
{
for (int i = 0; i < max; i++)
{
current = current + 1;
Dispatcher.Invoke(new Action(() => LoadProgress.Value = (double)(current / max) * 100));
}
}));
MessageBox.Show("Done!");
}

perform Action<> in main thread

I'm writing an app, that performs very long requests at background. After each request I need to send result to main form.
So, here is a code:
Form1.cs
private async void StartButton_Click(object sender, EventArgs e)
{
await Logic.GenerateStackAsync(stackSettings, delegate(FullOrder transaction)
{
lastOrderId.Text = transaction.OrderId;
}
);
MessageBox.Show("Completed!");
}
Logic.cs:
public static bool GenerateStack(StackSettings stackSettings, Action<FullOrder> onOrderCreated = null)
{
for(var i = 0; i < 10; i++)
{
// long, long request, replaced with:
System.Threading.Thread.Sleep(10000);
if (onOrderCreated != null)
{
onOrderCreated.Invoke(order);
// tried to change it with onOrderCreated(order), no results.
}
}
return true;
}
public static Task<bool> GenerateStackAsync(StackSettings stackSettings, Action<FullOrder> onOrderCreated)
{
return TaskEx.Run(() => GenerateStack(stackSettings, onOrderCreated));
}
It throws an exception: "Control 'lastOrderId' accessed from a thread other than the thread it was created on.", which can be fixed by adding CheckForIllegalCrossThreadCalls = false;, but I think that this is a bad experience. How make it right? Thank you in advance.
P.S. Sorry for bad English.
First, do not expose (fake-)asynchronous wrappers for your synchronous methods.
Next, if you want to report progress updates, then use the progress update classes provided in .NET for that purpose.
public static bool GenerateStack(StackSettings stackSettings, IProgress<FullOrder> progress = null)
{
for(var i = 0; i < 10; i++)
{
// long, long request, replaced with:
System.Threading.Thread.Sleep(10000);
if (progress != null)
{
progress.Report(order);
}
}
return true;
}
Then, you can call it as such:
private async void StartButton_Click(object sender, EventArgs e)
{
var progress = new Progress<FullOrder>(transaction =>
{
lastOrderId.Text = transaction.OrderId;
});
await Task.Run(() => Logic.GenerateStack(stackSettings, progress));
MessageBox.Show("Completed!");
}
I would say that you need to use Control.Invoke to solve that problem:
See http://msdn.microsoft.com/library/system.windows.forms.control.invoke(v=vs.110).aspx
when you use async\await u actually starting new thread you do you stuff there and you want the result to show in the main thread the UIThread thats why you need to use the Control.Invoke

detect when each task is complete

I want to update a progressbar as each task is completed below.
The method var continuation2 = Task.Factory.ContinueWhenAny(..... doesnt work.
What is the correct way to do this?
C# Code
private void radButtonInsertManyErrors_Click(object sender, EventArgs e)
{
try
{
radProgressBarStatus.Maximum = int.Parse(radTextBoxNumberofErrorsInsert.Text);
radProgressBarStatus.Value1 = 0;
Task<int>[] tasks = new Task<int>[int.Parse(radTextBoxNumberofErrorsInsert.Text)];
for (int i = 0; i < int.Parse(radTextBoxNumberofErrorsInsert.Text); i++)
{
int x = i;
tasks[i] = new Task<int>(() =>
{
//insert the error into table FA_Errors
Accessor.Insert_FAErrors(BLLErrorCodes.BLL_Error_Codes.Error_Log_Event_Login.ToString(),
(int)BLLErrorCodes.BLL_Error_Codes.Error_Log_Event_Login,
"Some Error", "",
MethodBase.GetCurrentMethod().DeclaringType.Namespace.ToString(),
MethodBase.GetCurrentMethod().Name.ToString(),
BLLErrorCategory.BLL_Error_Category.WEB_APP.ToString(),
"pc source", "damo",
sConn.ToString());
return 1;
});
}
var continuation = Task.Factory.ContinueWhenAll(
tasks,
(antecedents) =>
{
RadMessageBox.Show("Finished inserting errors ");
});
var continuation2 = Task.Factory.ContinueWhenAny(
tasks,
(antecedents) =>
{
radProgressBarStatus.Value1++;
});
for (int i = 0; i < int.Parse(radTextBoxNumberofErrorsInsert.Text); i++)
tasks[i].Start();
// Use next line if you want to block the main thread until all the tasks are complete
//continuation.Wait();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
}
You can use this function:
public static void TaskProgress(IEnumerable<Task> tasks, Action<int> callback)
{
int count = 0;
foreach (var task in tasks)
task.ContinueWith(t => callback(Interlocked.Increment(ref count)));
}
It will call the callback each time a task completes with the number of currently completed tasks. Note that the callbacks are not synchronized, so it can be called while the previous callback is still running.
Set up a continuation with each of the tasks. Keep a (thread-safe) counter on how many completed and update the UI on completion of each task.
Actually, Task.WhenAll does keep such a counter under the hood. It is just not accessible.

Categories

Resources