Get the HTML Document of WebView2 inside a Thread - c#

The function should get the HTML Document, working on a Thread.
The ExecuteScriptAsync needs to manage a Task which cause execution error if I run it on a separate thread. I do not know how to get the HTML content synchronously either.
Which would be the best practice solution?
Thread thread = new Thread(() => Work());
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
protected void Work()
{
// ...
GetHtmlDocument();
// ...
}
protected string GetHtmlDocument()
{
if (webBrowserEdge.InvokeRequired)
{
return (string)webBrowserEdge.Invoke(
new Func<String>(() => GetHtmlDocument())
);
}
else
{
string script = "document.documentElement.outerHTML";
return webBrowserEdge.ExecuteScriptAsync(script); // compilation error
}
}

You want to run a task synchronously? Change GetHtmlDocument's return type to Task<string> and use this code in method Work:
using (var task = GetHtmlDocument()) {
task.RunSynchronously();
string htmlDocument = task.Result;
}
This code runs the task in the thread where it was created (synchronously). The thread is blocked until the task is completed.

Related

First time using Parallel.ForEach UI still locking up

What I am trying to do here is loop a listview full of URLs, check if the source code contains a string, if it does then update the UI listview with YES or NO.
I had forgotten about the Parallel.ForEach method, so decided to try it out (I'm not even sure if it's the best solution for this)
Parallel.ForEach(listViewMain.Items.Cast<ListViewItem>(), row =>
{
try
{
string html = Helpers.GetRequest(row.Text);
if (html.Contains(txtBoxFind.Text))
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
} catch(Exception) {
}
});
The process is fairly simple doing it without the Parallel.ForEach but the UI is still locking up, have i implemented it right? Helpers.GetRequest simply returns the raw HTML to be checked, i thought using the Parallel.ForEach would stop the UI locking while processing or have i got it wrong, any help is appreciated.
Before we begin, do note that Parallel.ForEach in itself is a blocking call, so that is why you experience the UI being not responsive.
A good read is Avoid Executing Parallel Loops on the UI Thread from the MS docs:
... the parallel loop blocks the UI thread on which it’s executing until all iterations are complete.
That said, you should use a Task based approach if not dealing with CPU bound work but with I/O bound work. Since you are dealing with network calls you should stick to tasks. Try this:
public async Task DoSomething()
{
// Process the items parallel
await Task.WhenAll(listViewMain.Items.Cast<ListViewItem>().Select(async row =>
{
// wrap the long running call in a async Task
string html = await Task.Run(() => Helpers.GetRequest(row.Text));
// no need for context capturing and invokes, this is running on the UI thread
var containsText = html.Contains(txtBoxFind.Text);
if (containsText)
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
}));
}
It would be even better when you can make Helpers.GetRequest(row.Text) a Task based method, then you could do:
public async Task DoSomething()
{
// Process the items parallel
await Task.WhenAll(listViewMain.Items.Cast<ListViewItem>().Select(async row =>
{
// wrap the long running call in a async Task
string html = await Helpers.GetRequestAsync(row.Text);
// no need for context capturing and invokes, this is running on the UI thread
var containsText = html.Contains(txtBoxFind.Text);
if (containsText)
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
}));
}
but we need to see the code of Helpers.GetRequest(row.Text) to assist you with that.
EDIT
You've shown the code of GetRequest. WebClient is not task based, try HttpClient:
public async Task<string> GetRequestAsync(string url)
{
var html = "";
using (HttpClient wc = new HttpClient())
{
html = await wc.GetStringAsync(url);
}
return html;
}
the Parallel.ForEach is executing on the UI thread (current thread) and it will not provide more performance for you in case of non-blocking UI. If you want to avoid the UI block, you could try using the async methods, for sample:
Task.Run(() => CheckItems());
Given you can implement an async version of the GetRequest method, you could implement an async method to do this, for sample:
public async Task CheckItems()
{
foreach (var row in listViewMain.Items.Cast<ListViewItem>())
{
try
{
string html = await Helpers.GetRequestAsync(row.Text);
if (html.Contains(txtBoxFind.Text))
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
} catch(Exception ex) {
}
}
}
Don't use the Parallel.ForEach() for this. Parallel.ForEach() is used for CPU bound work. Not IO.
I would do something like: (I didn't tested it, might contains some typo's) (used notepad)
So you might use this for the idea:
public async void Button_Click(object sender, EventArg e)
{
await CheckItems(listViewMain.Items.Cast<ListViewItem>());
}
public async Task CheckItems(IEnumerable<ListViewItem> items)
{
// Capture the UI thread synchronization.
var context = SynchronizationContext.Current;
var tasks = new List<Task>();
// create tasks.
foreach (var row in items)
{
tasks.Add(Task.Run(() =>
{
// the lookup on a (probably) threadpool thread
string html = Helpers.GetRequest(row.Text);
// the processing here..
var containsText = html.Contains(txtBoxFind.Text);
// post the result (and touching gui items in the UI thread)
// this.Invoke() is also and might be the best solution.
context.Post(() =>
{
if (containsText)
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
});
}));
}
// wait for them
await Task.WhenAll(tasks);
}
While I was adding some comment, you could also use the this.Invoke() for this (instead of the SynchronizationContext)
gtg.

C# skips DispatcherOperation

I have a big time-consuming task and I try to implement asynchronous methods in order to prevent the application from blocking. My code looks like this:
CancellationTokenSource _cts;
async void asyncMethod()
{
// ..................
_cts = new CancellationTokenSource();
var progress = new Progress<double>(value => pbCalculationProgress.Value = value);
try
{
_cts.CancelAfter(25000);
int count = await awaitMethod(_cts.Token, progress);
}
catch (OperationCanceledException ex)
{
// .......
}
finally
{
_cts.Dispose();
}
// ..................
}
async Task<int> awaitMethod(CancellationToken ct, IProgress<double> progress)
{
var task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
sqlParser();
progress.Report(1);
return 0;
});
return await task;
}
void sqlParser()
{
string info = form1TxtBox.Text;
// ................
}
Also, the program throws an exception, because sqlParser() updates UI thread, when it retrieves the text from the form. The solution is to introduce Dispatcher method, which allows UI update. I keep the body of awaitMethod the same and simply put sqlParser() inside of the Dispatcher:
DispatcherOperation op = Dispatcher.BeginInvoke((Action)(() =>
{
sqlParser();
}));
Here happens something interesting: asyncMethod() even doesn't dare to call awaitMethod! However, if I put a breakpoint inside of sqlParser() and run debugger, then everything goes very smoothly.
Please, can somebody explain what I miss in my code? What kind of patch should i use to make Dispatcher work correctly? Or: how can I run my program without Dispatcher and without throwing UI-update exception?
The solution is to introduce Dispatcher method, which allows UI update.
That's never a good solution. Having background threads reaching directly into your UI is encouraging spaghetti code.
how can I run my program without Dispatcher and without throwing UI-update exception?
Think of your background thread code as its own separate component, completely separate from the UI. If your background code needs data from the UI, then have your UI code read it before the background code starts, and pass that data into the background code.
async void asyncMethod()
{
...
try
{
var data = myUiComponent.Text;
_cts.CancelAfter(25000);
int count = await awaitMethod(data, _cts.Token, progress);
}
...
}
async Task<int> awaitMethod(string data, CancellationToken ct, IProgress<double> progress)
{
var task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
sqlParser(data);
progress.Report(1);
return 0;
});
return await task;
}

WhenAll() not working as expected

I need to make UI thread wait until a task array completes execution.The problem with below code is that - the tasks inturn invoke UI thread to write into textbox. How to fix this?
public partial class FormConsole : Form
{
public FormConsole()
{
InitializeComponent();
}
void txtSayHello_Click(object sender, EventArgs e)
{
Class1 objclss = new Class1();
objclss.formConsole = this;
Task[] taa = new Task[4];
taa[0] = new Task(() => objclss.DoSomeThigs("Hello world"));
taa[1] = new Task(() => objclss.DoSomeThigs("Hello world1"));
taa[2] = new Task(() => objclss.DoSomeThigs("Hello world2"));
taa[3] = new Task(() => objclss.DoSomeThigs("Hello world3"));
foreach(Task task in taa)
{
task.Start();
}
Task.WhenAll(taa);
this.txtConsole.AppendText("All threads complete");
}
delegate void doStuffDelegate(string value);
public void doStuff(string value)
{
if (System.Windows.Forms.Form.ActiveForm.InvokeRequired && IsHandleCreated)
{
BeginInvoke(new doStuffDelegate(doStuff), value);
}
else
txtConsole.AppendText(value);
}
}
public class Class1
{
public FormConsole formConsole;
public void DoSomeThigs(string sampleText)
{
formConsole.doStuff(sampleText);
}
}
o/p now : Console Redirection TestAll threads completeHello worldHello world1Hello world2Hello world3
o/p I want : Console Redirection TestHello worldHello world1Hello world2Hello world3All threads complete
What's the solution?
Task.WhenAll returns a task that completes when all tasks passed to it complete. You have to await this task, otherwise the method will continue executing.
async void txtSayHello_Click(object sender, EventArgs e)
{
...
await Task.WhenAll(taa);
...
}
There is a blocking version of this method - Task.WaitAll. It will block the current thread until all tasks are done, but it's not a good idea to block the UI thread.
Also, the preferred way to start a task on a thread pool thread is to use Task.Run.
Task.WhenAll returns a Task representing the completion of all these tasks in the enumerable. You need to await that task as the method doesn't block the thread.
Turn txtSayHello_Click into an async void (which should only be used for event handlers) method and await the task returned from Task.WhenAll:
async void txtSayHello_Click(object sender, EventArgs e)
{
// ...
await Task.WhenAll(taa);
// ...
}
Moreover, you should almost always avoid using the Task constructor. You should use Task.Factory.StartNew with TaskScheduler.FromSynchronizationContext if you need the task to run on the UI thread (that depends on what you actually do with FormConsole) or Task.Run if you don't. Meaning:
taa[0] = Task.Factory.StartNew(() => objclss.DoSomeThigs("Hello world"), CancellationToken.None, TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
Or:
taa[0] = Task.Run(() => objclss.DoSomeThigs("Hello world"));
You're most likely confusing WhenAll() with WaitAll().
As already suggested you can use async with await or you can simply use :
A) Task.WhenAll(taa).Wait();
B) Task.WaitAll(taa);
But in your case this will block UI thread. So it's better to put rest of the code to Continuation Task and invoke UI operations with Control.Invoke() :
Task.WhenAll(taa).ContinueWith(t =>
{
this.Invoke(() => this.txtConsole.AppendText("All threads complete"));
});

Method that executes a continuation of a Task in the main thread

I have to create a method, that similar to ContinueWith(), but will execute continuation in main thread, after main Task.
How can I do that?
I could endlessly checking the state of Task in my method, and when it finishes start continuation, but I think it couldn`t work in such way:
Task<DayOfWeek> taskA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);
Task<string> continuation = taskA.OurMethod((antecedent) =>
{
return String.Format("Today is {0}.", antecedent.Result);
});
// Because we endlessly checking state of main Task
// Code below will never execute
taskA.Start();
So what I could do here?
Try passing around the "main" thread's Dispatcher. Example:
Task.Factory.StartNew(()=>
{
// blah
}
.ContinueWith(task=>
{
Application.Current.Dispatcher.BeginInvoke(new Action(()=>
{
// yay, on the UI thread...
}
}
Assuming that the "main" thread is UI thread. If it's not, then grab that thread's dispatcher after you make it. Use that dispatcher instead of Application.Current's (i.e. CurrentDispatcher).
You can create an ExtensionMethod for a process like this. Here is an example implementation
static class ExtensionMethods
{
public static Task ContinueOnUI(this Task task, Action continuation)
{
return task.ContinueWith((arg) =>
{
Dispatcher.CurrentDispatcher.Invoke(continuation);
});
}
}
Consume it like this.
Task run = new Task(() =>
{
Debug.WriteLine("Testing");
});
run.ContinueOnUI(() =>
{
Notify += "\nExecuted On UI"; // Notify is bound on a UI control
});
run.Start();

Run "async" method on a background thread

I'm trying to run an "async" method from an ordinary method:
public string Prop
{
get { return _prop; }
set
{
_prop = value;
RaisePropertyChanged();
}
}
private async Task<string> GetSomething()
{
return await new Task<string>( () => {
Thread.Sleep(2000);
return "hello world";
});
}
public void Activate()
{
GetSomething.ContinueWith(task => Prop = task.Result).Start();
// ^ exception here
}
The exception thrown is:
Start may not be called on a continuation task.
What does that mean, anyway? How can I simply run my async method on a background thread, dispatch the result back to the UI thread?
Edit
Also tried Task.Wait, but the waiting never ends:
public void Activate()
{
Task.Factory.StartNew<string>( () => {
var task = GetSomething();
task.Wait();
// ^ stuck here
return task.Result;
}).ContinueWith(task => {
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
GetSomething.ContinueWith(task => Prop = task.Result).Start();
}
To fix your example specifically:
public void Activate()
{
Task.Factory.StartNew(() =>
{
//executes in thread pool.
return GetSomething(); // returns a Task.
}) // returns a Task<Task>.
.Unwrap() // "unwraps" the outer task, returning a proxy
// for the inner one returned by GetSomething().
.ContinueWith(task =>
{
// executes in UI thread.
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
This will work, but it's old-school.
The modern way to run something on a background thread and dispatch back to UI thread is to use Task.Run(), async, and await:
async void Activate()
{
Prop = await Task.Run(() => GetSomething());
}
Task.Run will start something in a thread pool thread. When you await something, it automatically comes back in on the execution context which started it. In this case, your UI thread.
You should generally never need to call Start(). Prefer async methods, Task.Run, and Task.Factory.StartNew -- all of which start the tasks automatically. Continuations created with await or ContinueWith are also started automatically when their parent completes.
WARNING about using FromCurrentSynchronizationContext:
Ok, Cory knows how to make me rewrite answer:).
So the main culprit is actually the FromCurrentSynchronizationContext!
Any time StartNew or ContinueWith runs on this kind scheduler, it runs on the UI Thread. One may think:
OK, let's start subsequent operations on UI, change some controls, spawn some operations. But from now TaskScheduler.Current is not null and if any control has some events, that spawn some StartNew expecting to be running on ThreadPool, then from there it goes wrong. UI aps are usually complex, unease to maintain certainty, that nothing will call another StartNew operation, simple example here:
public partial class Form1 : Form
{
public static int Counter;
public static int Cnt => Interlocked.Increment(ref Counter);
private readonly TextBox _txt = new TextBox();
public static void WriteTrace(string from) => Trace.WriteLine($"{Cnt}:{from}:{Thread.CurrentThread.Name ?? "ThreadPool"}");
public Form1()
{
InitializeComponent();
Thread.CurrentThread.Name = "ThreadUI!";
//this seems to be so nice :)
_txt.TextChanged += (sender, args) => { TestB(); };
WriteTrace("Form1"); TestA(); WriteTrace("Form1");
}
private void TestA()
{
WriteTrace("TestA.Begin");
Task.Factory.StartNew(() => WriteTrace("TestA.StartNew"))
.ContinueWith(t =>
{
WriteTrace("TestA.ContinuWith");
_txt.Text = #"TestA has completed!";
}, TaskScheduler.FromCurrentSynchronizationContext());
WriteTrace("TestA.End");
}
private void TestB()
{
WriteTrace("TestB.Begin");
Task.Factory.StartNew(() => WriteTrace("TestB.StartNew - expected ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith1 should be ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith2"));
WriteTrace("TestB.End");
}
}
Form1:ThreadUI! - OK
TestA.Begin:ThreadUI! - OK
TestA.End:ThreadUI! - OK
Form1:ThreadUI! - OK
TestA.StartNew:ThreadPool - OK
TestA.ContinuWith:ThreadUI! - OK
TestB.Begin:ThreadUI! - OK
TestB.End:ThreadUI! - OK
TestB.StartNew - expected ThreadPool:ThreadUI! - COULD BE UNEXPECTED!
TestB.ContinueWith1 should be ThreadPool:ThreadUI! - COULD BE UNEXPECTED!
TestB.ContinueWith2:ThreadUI! - OK
Please notice, that tasks returned by:
async method,
Task.Fatory.StartNew,
Task.Run,
can not be started! They are already hot tasks...

Categories