WebView2.ExecuteStriptAsync task blocked forewer - c#

When I try to read the content of one web page loaded into the webview2 control, task ExecuteScriptAsync blocks forever. After this the application does not respond, but the site is still operational. The site is posted on an corprate intranet, so I can't provide the URL here. It is Ivanti Service Desk.
private void bnNewReguest_Click(object sender, EventArgs e)
{
var t = GetTextAsync();
string sHtml = t.Result;
if (!sHtml.Contains("shortcutItem_12345"))
{
MessageBox.Show("Please wait for the page to load");
return;
}
webView21.ExecuteScriptAsync("document.getElementById('shortcutItem_12345').click()");
}
private async Task<string> GetTextAsync()
{
if (webView21.CoreWebView2 == null)
{
MessageBox.Show("Wait a moment...");
return "";
}
var script = "document.documentElement.outerHTML";
string sHtml = await webView21.CoreWebView2.ExecuteScriptAsync(script); // deadlock
string sHtmlDecoded = System.Text.RegularExpressions.Regex.Unescape(sHtml);
return sHtmlDecoded;
}
I also tried the code below, but the result is similar.
string sHtml = await webView21.CoreWebView2.ExecuteScriptAsync(script).ConfigureAwait(false);
The WebView2 version is 1.0.1418.22. How can I protect from deadlock?
I found a thread about the same problem here, but none of the solutions work for me.

I describe this deadlock on my blog. The best solution is to not block on asynchronous code.
In your case, this could look like this:
private async void bnNewReguest_Click(object sender, EventArgs e)
{
string sHtml = await GetTextAsync();
if (!sHtml.Contains("shortcutItem_12345"))
{
MessageBox.Show("Please wait for the page to load");
return;
}
await webView21.ExecuteScriptAsync("document.getElementById('shortcutItem_12345').click()");
}

Related

What is causing the deadlock?

I know this is not using the proper form, but can someone tell me why the first code snippet works and the 2nd causes a deadlock?
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text = DownloadStringV3("https://www.google.com");
}
public string DownloadStringV3(String url)
{
var resp = new HttpClient().GetAsync(url).Result;
return resp.Content.ReadAsStringAsync().Result;
}
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text = DownloadStringV3("https://www.google.com").Result;
}
public async Task<string> DownloadStringV3(String url)
{
var resp = await new HttpClient().GetAsync(url);
return await resp.Content.ReadAsStringAsync();
}
Different synchronization contexts? This is using .NET 4.8
Every time you await something, you say, I am temporary done here, give some other thread a chance to execute and then continue from here, when the async task is completed.
So in your case await new HttpClient().GetAsync(url) switches the the ui thread and waiting for DownloadStringV3("https://www.google.com").Result be done, which will never be the case, because we do never execute return await resp.Content.ReadAsStringAsync().
Thats why you should do async all the way to the root and never call Result.
The first code works, because you block on the UI thread and don't try to switch to another thread, while waiting for it.
To make the second one working you need to:
private async void button1_Click(object sender, EventArgs e)
{
// make with try catch sure, you catch any error, async void is special. Better is async Task
textBox1.Text = await DownloadStringV3("https://www.google.com");
}
public async Task<string> DownloadStringV3(String url)
{
var resp = await new HttpClient().GetAsync(url);
return await resp.Content.ReadAsStringAsync();
}

How to prevent the application from freezing, while checking a lot of PDF files?

I try to create a program checking in a lot of PDF. It can be done from a network drive and take few minutes. The application freeze all during the process and I want to avoid that.
I searched in a lot of posts and videos but I failed to implement it in my program. I tried this code to understand how it works but it failed too...
async private void button1_Click(object sender, EventArgs e)
{
rtbx.AppendText($"Processing...\n");
// This webswite takes 1-2s to be loaded
await HeavyWork(#"https://aion.plaync.com/");
rtbx.AppendText($"End.\n");
}
public Task HeavyWork(string url)
{
List<string> lesinfos = new List<string>();
while (checkBox1.Checked == false)
{
using (WebClient web1 = new WebClient())
{
lesinfos.Add(web1.DownloadString(url));
}
rtbx.AppendText($"{lesinfos.Count}\n");
this.Refresh();
}
rtbx.AppendText($"Done !\n");
return Task.CompletedTask;
}
When I click the button, I am never able to click in the checkbox, and the UI never respond.
Taking into account that you are forced to use a synchronous API, you can keep the UI responsive by offloading the blocking call to a ThreadPool thread. The tool to use for this purpose is the Task.Run method. This method is specifically designed for offloading work to the ThreadPool. Here is how you can use it:
public async Task HeavyWork(string url)
{
List<string> lesinfos = new List<string>();
using (WebClient web1 = new WebClient())
{
while (checkBox1.Checked == false)
{
string result = await Task.Run(() => web1.DownloadString(url));
lesinfos.Add(result);
}
rtbx.AppendText($"{lesinfos.Count}\n");
this.Refresh();
}
rtbx.AppendText($"Done !\n");
}
Notice the async keyword in the signature of the HeavyWork method. Notice the await before the Task.Run call. Notice the absence of the return Task.CompletedTask line at the end.
If you are unfamiliar with the async/await technology, here is a tutorial to get you started: Asynchronous programming with async and await.
Try using DownloadStringTaskAsync(...) with an `await (see example below).
public async Task HeavyWork(string url)
{
List<string> lesinfos = new List<string>();
while (checkBox1.Checked == false)
{
using (WebClient web1 = new WebClient())
{
var content = await web1.DownloadStringTaskAsync(url);
lesinfos.Add(content);
}
rtbx.AppendText($"{lesinfos.Count}\n");
this.Refresh();
}
rtbx.AppendText($"Done !\n");
}
note: WebClient is considered obsolete and it's now recommended to use HttpClient.

Xamarin.Forms GetAsync stops debugging without error

My MainPage code-behind:
private void Button_Clicked(object sender, EventArgs e)
{
Query().Wait();
App.Current.MainPage = new Categories();
}
public async Task Query()
{
restaurantsClient = (App.Current as App).restaurantsClient;
try
{
var restaurantsNames = await restaurantsClient.GetCatalogsAsync(1);
}
catch (Exception ex)
{
var x = 0;
}
}
I tried this code too but didn't work, happens the same problem:
async static Task GetRequest(String URL)
{
using (HttpClient client = new HttpClient())
{
// As soon as I try to step over (or even into) this next line, it crashes.
using (HttpResponseMessage response = await client.GetAsync(URL))
{
using (HttpContent content = response.Content)
{
string data = await content.ReadAsStringAsync();
Console.WriteLine(data);
}
}
}
}
Rest-API in C#:
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
When the project reach this line it just die without showing any error.
If I do it in the browser it works.
The API is running locally in my PC (Im using Conveyor to expose the API).
Ok I make a video to see better what Im talking about:
https://youtu.be/ONKTipPsEXI
As you can see after I click Next Step in response line stop executing the rest of the code.
That's because you're using .Wait() of a Task on the UI thread (Button.Clicked event is handled on the UI thread) causing a deadlock. The task is waiting for the UI thread to give it control and the UI thread is waiting for the task to complete. The solution to this is to add async modifier to your event handler and use await Query() instead of Query().Wait().
private async void Button_Clicked(object sender, EventArgs e)
{
await Query();
App.Current.MainPage = new Categories();
}
I'd also recommend reading this article by Stephen Cleary about this matter. Moreover, he's made a fabulous series (A Tour of Task) about C# tasks in general.
UPDATE:
After OP's question update and discussion in this answer's comments; he thinks that there's a problem because he can reach the end of GetCatalogsAsync(int) before the end of GetCatalogsAsync(int, CancellationToken). That's completely natural and is to be expected. Solution:
public async System.Threading.Tasks.Task<CatalogsInCategory> GetCatalogsAsync(int id)
{
return await GetCatalogsAsync(id, System.Threading.CancellationToken.None);
}

Calling async method from non async method

Here is my code in doing so but it seems like I could not do other things once it started calling the async function and then the app will not respond. I would like to just run it to the background.
I'm doing a search and in every 3 letters, it will call the api to get datas if have match. Once I have input 3 letters, it then it calls to the API and I could not input more letters because the app is not responding.
How to call the async function and will just run in the background so that I could still search.
void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
var newText = e.NewTextValue;
//once 3 keystroke is visible by 3
if (newText.Length % 3 == 0)
{
//Call the web services
var result = GettingModel(newText);
if (result != null || result != string.Empty)
{
ModelVIN.Text = result;
}
}
}
private string GettingModel(string vinText)
{
var task = getModelForVIN(vinText);
var result = task.Result;
return result.Model;
}
private async Task<VINLookUp> getModelForVIN(string vinText)
{
var deviceId = CrossDeviceInfo.Current.Model;
deviceId = deviceId.Replace(" ", "");
var requestMgr = new RequestManager(deviceId);
var VinData = new VINLookUp();
VinData = await requestMgr.getModelForVIN(vinText);
return VinData;
}
Thanks in advance for the help.
You don't need the GettingModel(string vinText) method.
By calling the Task.Result you are blocking the main thread.
Calling .Result in the UI thread will likely deadlock everything which is what you are experiencing. Use ContinueWith or async void with await.
You can make your Entry_TextChanged async and await the web request so that it doesn't block the UI.
You can even run it on a separate thread and use ContinueWith() if you don't require to make the make user wait for the operation to complete. If you are going that route make sure you use Device.BeginInvookeOnMainThread() to run any code that needs to be run on UI thread.
The better code would be :
private async void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
var newText = e.NewTextValue;
//once 3 keystroke is visible by 3
if (newText.Length % 3 == 0)
{
//Call the web services
var result = await GetModelStringForVIN(newText);
if (string.IsNullOrEmpty(result) == false)
{
ModelVIN.Text = result;
}
}
}
private async Task<string> GetModelStringForVIN(string vinText)
{
var deviceId = CrossDeviceInfo.Current.Model;
deviceId = deviceId.Replace(" ", string.Empty);
var requestMgr = new RequestManager(deviceId);
var VinData = await requestMgr.getModelForVIN(vinText);
return VinData?.Model;
}
The following links would help you understand the concepts better :
Xamarin Async Support Overview
Asynchronous Operations with Xamarin

Async Events Causing Deadlock When Synchronization is Needed

I have a class that I created to consume a REST API. I wrote the class to communicate asynchronously with the web service, since I didn't originally think I needed to have anything run synchronized. Now I am experiencing a situation where I realized using an asynchronous method is not ideal for one particular situation in my application since it runs out of order and causes exceptions since the application is attempting to call a method that it is not ready for. I'm not 100% sure why this is happening, but I think it's due to these methods being called in async void events within my UI. Here are some code snippets that show an example of the situation:
class MyForm : Form
{
private RestConnection connection;
private async void MyForm_Load(object sender, EventArgs e)
{
if(connection == null)
using (LogOnDialog logOnDialog = new LogOnDialog())
{
var result = logOnDialog.ShowDialog(this);
if(result == DialogResult.OK)
{
connection = logOnDialog.Connection;
}
}
formComboBox.DataSource = await connection.GetChoices();
}
}
class LogOnDialog : Form
{
public RestConnection Connection {private set;get;}
private async void saveButton_Click(object sender, EventArgs e)
{
RestConnection conn = new RestConnection(userNameTB.Text, passwordTb.Text);
await conn.LogIn();
if(conn.LoggedIn) //issue here
{
Connection = conn;
DialogResult = DialogResult.OK;
this.Close();
}
else
{
Connection = null;
DialogResult = DialogResult.Abort;
MessageBox.Show("Invalid Credentials, Try Again.");
}
}
}
What's happening is that the application is attempting to call connection.GetOptions(), but connection is still null because the LogOnDialog's async event that creates the connection and check's for a successful login before allowing the connection to be offered to the caller. However, since connection is null since the Click event hasn't completed a NullReferenceException is called. Additionally, if I continue past and ignore the exception an ObjectDisposedException is thrown since we're now outside of the using block.
I attempted to force the logon to be synchronous by removing the async keyword from the event, and calling Wait() on the login Method. This caused a deadlock. I also tried to capture the task using the below code, and spinwait for it:
Task t = conn.LogOn();
while(!t.IsCompleted)
Thread.Sleep(50);
This didn't deadlock, but it did spin forever. Every time I checked the breakpoint on the While condition the Task's status was always WAITINGFORACTIVATION and essentially locked up the application. In order to get this working I'm going to create some synchronous methods for this situation, but what would allow this to work properly and be async all the way?
EDIT: Additional Code Snippet as Requested for LogOn() and GetOptions()
class RestConnection
{
private string user;
private string password
private XDocument convertToXDoc(string functionName, IDictionary<string,string> parameters) {} //not shown, but this just creates an XML document in the required format for the REST service to consume.
private async Task<XDocument> SendCommand(XDocument commandDocument)
{
XDocument responseData = null;
byte[] data = Encoding.UTF8.GetBytes(commandDoc.ToString());
HttpWebRequest request = WebRequest.CreateHttp(this.serverUrl);
request.Method = "POST";
request.ContentType = "text/xml";
request.ContentLength = data.Length;
using (var requestStream = await request.GetRequestStreamAsync())
{
await requestStream.WriteAsync(data, 0, data.Length);
}
HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse;
using (var responseStream = response.GetResponseStream())
{
responseData = XDocument.Load(responseStream);
}
return responseData;
}
public async Task LogIn()
{
var parameters = new Dictionary<string, string>();
parameters.Add("USERNAME", userName);
parameters.Add("PASSWORD", passWord);
parameters.Add("CORELICTYPE", String.Empty);
parameters.Add("REMOTEAUTH", "False");
var xmlCommand = ConvertMethodToXml("LoginUserEx3", parameters);
var response = await SendCommand(xmlCommand);
//read response
switch (response.Root.Element("RESULTS").Element("RESULTVAL").Value)
{
case "0":
sessionId = response.Root.Element("SESSIONID").Value;
pingRequired = response.Root.Element("PINGTIME").Value != "0";
if (pingRequired)
{
pingInterval = int.Parse(response.Root.Element("PINGTIME").Value);
pingTimer = new Timer(pingInterval);
pingTimer.Elapsed += PingServerRequired;
pingTimer.Start();
}
loggedIn = true;
break;
//removed other cases for example since they all throw exceptions
default:
loggedIn = false;
throw new ConnectionException("Error");
}
}
}
The GetOptions() in the same format as the LogIn() method, except it returns a Task<List<Options>> from parsing the returned XDocument.
The problem is here:
{
Connection = null;
DialogResult = DialogResult.Abort; //<<------ this
MessageBox.Show("Invalid Credentials, Try Again.");
}
Assigning DialogResult will automatically close your form with the result you pass in. Remove that line and you will be fine (especially if you want the dialog to never close).

Categories