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);
}
Related
private void button1_Click(object sender, EventArgs e)
{
backworker.RunWorkerAsync();
}
private void backworker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10000; i++)
{
label1.Text = i.ToString();
DateTime d2 = DateTime.Now;
label2.Text = d2.ToString();
}
}
Friends i am working on comparing times between when the task of backworker finished, like how much time it tooks to finish the loop task
but when i do it, i tried to put the Comparasion after loop but it tells me error because d2 not declared
so how can i solve that to compare and get the exact time that the loop took to finish the task of printing numbers
I think this could work:
DateTime d2 = DateTime.Now;
for (int i = 0; i < 10000; i++)
{
...
}
label2.Text = (DateTime.Now - d2).ToString();
A good way to measure times is to use System.Diagnostics.Stopwatch:
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10000; i++)
{
...
}
label2.Text = sw.Elapsed.ToString();
Your DateTime is declared withing the loop and therefore its scope is limited to the loop body. You must declare it before, i.e., outside, the loop to make it available after the loop. But it is better to use a Stopwatch for this purpose.
Another problem is that you are attempting to access a Control (a Label) from another thread than the UI thread. You are not allowed to do this.
Fortunately the BackgroundWorker Class can "Talk" to the UI thread through the ProgressChanged event.
Setup the BackgroundWorker with:
private void button1_Click(object sender, EventArgs e)
{
backworker.WorkerReportsProgress = true;
backworker.RunWorkerAsync();
}
Then declare another event handler which will automatically be called in the UI-thread:
private void Backworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = e.ProgressPercentage.ToString();
var ts = (TimeSpan)e.UserState;
label2.Text = ts.ToString(#"ss\.ff");
}
Now, change you worker to
private void Backworker_DoWork(object sender, DoWorkEventArgs e)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
for (int i = 0; i < 100; i++) {
Thread.Sleep(100);
backworker.ReportProgress(i, stopWatch.Elapsed);
}
stopWatch.Stop();
backworker.ReportProgress(100, stopWatch.Elapsed);
}
Note that I have introduced a Thread.Sleep(100); and have diminished the number of loops by 100. This is because otherwise the UI cannot display the progress that fast. In a real scenario you would replace Thread.Sleep by some useful work.
This question already has an answer here:
Why is cross thread operation exception not thrown while running exe in bin\Debug
(1 answer)
Closed 2 years ago.
I have simple Windows Form App in C# using VS 2019. When I run the application using F5, I face the exception
"System.InvalidOperationException: 'Cross-thread operation not valid: Control 'richTextBox1' accessed from a thread other than the thread it was created on.'".
But when I use Ctrl+F5 everything works fine.
Can anybody explain me why ?
Here is my code:
private async void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
for (int i = 0; i < 500; i++)
{
Thread.Sleep(1000);
richTextBox1.Text += string.Format("\n Row No: {0}", (i + 1));
}
});
Task.Run(() =>
{
for (int i = 0; i < 500; i++)
{
Thread.Sleep(1000);
richTextBox2.Text += string.Format("\n Row No: {0}", (i + 1));
}
});
}
When you use Ctrl+F5 you are starting without a debugger. The .net code does not throw the exception if the debugger is not attached.. However just because no error is thrown does not mean you should do it. It is still a bug in your code you should fix.
If you are curious the "correct way to do this" is not to use Task.Run and instead just use async methods.
private async void button1_Click(object sender, EventArgs e)
{
Task task1 = UpdateTextBox1();
Task task2 = UpdateTextBox2();
await Task.WhenAll(task1, task2);
}
private async Task UpdateTextBox1()
{
for (int i = 0; i < 500; i++)
{
await Task.Delay(1000);
richTextBox1.Text += string.Format("\n Row No: {0}", (i + 1));
}
});
private async Task UpdateTextBox2()
{
for (int i = 0; i < 500; i++)
{
await Task.Delay(1000);
richTextBox2.Text += string.Format("\n Row No: {0}", (i + 1));
}
}
I'm getting this error (in Title), here's my code
public async Task<int> ReadTextFile()
{
//
// Read in a file line-by-line, and store it all in a List.
//
var watch = Stopwatch.StartNew();
using (StreamReader reader = new StreamReader("Users.txt"))
{
String line;
while ((line = await reader.ReadLineAsync()) != null)
{
String[] splitLines = line.Split(' ');
User user;
if (splitLines.Length == 3)
{
user = new User
{
FirstName = splitLines[0],
MiddleName = splitLines[1],
LastName = splitLines[2]
};
}
else if (splitLines.Length == 2)
{
user = new User
{
FirstName = splitLines[0],
LastName = splitLines[1]
};
}
else
{
user = new User
{
FirstName = splitLines[0]
};
}
users.Add(user);
}
}
int number = 0;
for (int i = 0; i < 6000; i++)
{
for (int j = 0; j < 1000; j++)
{
for (int k = 0; k < 1000; k++)
{
number += 1;
}
}
}
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
return number;
}
public async Task<List<User>> GetUsers()
{
await this.ReadTextFile();
return this.users;
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MainWindowViewModel viewModel = new MainWindowViewModel();
var x = await viewModel.GetUsers();//ERROR IS ON THIS LINE
//dataGrid.ItemsSource = await viewModel.GetUsers();
}
}
Going by the example here https://msdn.microsoft.com/en-us/library/hh524395.aspx
My code should work, so I'm not sure what's happening, can someone please help?
The var x = await viewModel.GetUsers(); must also be on an async method. You can't await in a method that is not marked as async.
Update: After your update with more code, you are trying to await a method in your Button_Click method.
For your specific case, you need to mark your Button_Click as async. Note that async void is not recommended (it's always better to return a Task), but this is the specific case why async void is supported: to allow delegate with a specific void signature (which you can't change) to be marked as async. So just:
private async void Button_Click(object sender, RoutedEventArgs e)
{
// ...
var x = await viewModel.GetUsers();
// ...
}
When you await on something (on an awaitable), the important part is that the method where you await is marked async. That's exactly what the compiler error is telling you: read it carefully:
The await operator can only be used in a method or lambda marked with the 'async' modifier
It doesn't say (which seems it's what you understood):
The await operator can only be used when calling a method marked with the 'async' modifier
Generally, you'll await on a Task (or Task<T>): that's what you are doing there. The fact that GetUsers is marked async or not, is irrelevant: the important part is that it returns an awaitable (again, typically a Task: in your case a Task<List<User>>).
Whether that method (GetUsers) needs to be marked async or not depends only on whether that method itself uses await (in your case, it does), not on whether the caller method will await on its result or not.
If the MSDN is not clear to you, at the very least, I recommend that you carefully read and understand the official Async/Await FAQ (it's not really official but it comes from Microsoft and I found it easier to understand than the MSDN, which I reckon might be a bit dense sometimes-).
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
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 ...));
}