I'm trying to get some content on a website which is rendered by JavaScript. So I'm running a WebView with Visibility=Collapsed. I want to wait unitl NavigationCompleted and run some JavaScript then return the value.
The code look like:
private async void Foo()
{
// Want to get value here
var content = await GetContent();
}
private async Task<string> GetContent()
{
string content;
async void handler(WebView sender, WebViewNavigationCompletedEventArgs args)
{
content = await webView.InvokeScriptAsync("eval", new string[] { script });
webView.NavigationCompleted -= handler;
}
webView.NavigationCompleted += handler;
webView.Navigate(uri);
return content;
}
Since there is no await in GetContent(), the function always returns before NavigationCompleted fired.
You could use a SemaphoreSlim to asynchronously wait for the NavigationCompleted to get raised and handled:
private async Task<string> GetContent()
{
string content;
using (SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0, 1))
{
async void handler(WebView sender, WebViewNavigationCompletedEventArgs args)
{
content = await webView.InvokeScriptAsync("eval", new string[] { script });
webView.NavigationCompleted -= handler;
semaphoreSlim.Release();
}
webView.NavigationCompleted += handler;
webView.Navigate(uri);
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
}
return content;
}
I think you should use TaskCompletionSource. Create a source, and set its result at the end of the event handler, after you execute the script. Before you return the content, await the task of the task completion source.
If you want to wait for something, you can use ManualResetEvent. Just ensure that you don't use ManualResetEvent.WaitOne on the UI thread as it would hang the app.
Related
How can I wait for an async task to complete without freezing the whole Application?
This function works but Cout() gets called while the File is still downloading.
private void Btn_test_Click(object sender, EventArgs e)
{
var task = Task.Run(async () => { await DownloadWebFile("https://speed.hetzner.de/100MB.bin", AppDomain.CurrentDomain.BaseDirectory + "//100MB.bin"); });
Cout(DownloadSuccessMsg);
}
when I do this the whole Application freezes:
private void Btn_test_Click(object sender, EventArgs e)
{
var task = Task.Run(async () => { await DownloadWebFile("https://speed.hetzner.de/100MB.bin", AppDomain.CurrentDomain.BaseDirectory + "//100MB.bin"); });
task.Wait();
Cout(DownloadSuccessMsg);
}
How can I wait correctly before running other code depending on the downloaded file?
private static async Task DownloadWebFile(string url, string fullPath)
{
using var client = new DownloadManager(url, fullPath);
client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) =>
{
SetProgressBarValue((int)progressPercentage);
};
await client.StartDownload();
}
You can mark the method as async void. Returning void from an asynchronous method is usually not a great idea, but in the case of an event handler it's usually considered acceptable.
private async void Btn_test_Click(object sender, EventArgs e)
{
await DownloadWebFile("https://speed.hetzner.de/100MB.bin", AppDomain.CurrentDomain.BaseDirectory + "//100MB.bin");
Cout(DownloadSuccessMsg);
}
I have a problem. scriptEventClick does not see the method in scriptGlobal.
How should I know that scriptGlobal is registered in JS so that I can later call EvaluateScriptAsPromiseAsync
public Task<bool> GetResultAfterPageLoad(string pageUrl)
{
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
EventHandler<LoadingStateChangedEventArgs> onPageLoaded = null;
onPageLoaded = async (sender, args) =>
{
if (!args.IsLoading)
{
_browser.LoadingStateChanged -= onPageLoaded;
// initialization script
_browser.ExecuteScriptAsyncWhenPageLoaded(scriptGlobal);
// script for click button
JavascriptResponse responseClick = await _browser.EvaluateScriptAsPromiseAsync(scriptEventClick);
tcs.SetResult(_result);
}
};
_browser.LoadingStateChanged += onPageLoaded;
_browser.Load(pageUrl);
return tcs.Task;
}
where
scriptGlobal = "async function eventClick(){}";
scriptEventClick = "return eventClick();";
this solution works. but I think this is a bad answer. because I don't want to use delays in a loop
_browser.ExecuteScriptAsyncWhenPageLoaded(scriptGlobal);
JavascriptResponse responseGlobal = null;
do
{
responseGlobal = await _browser.EvaluateScriptAsync("eventClick");
await Task.Delay(100);
}
while (responseGlobal.Result == null);
JavascriptResponse responseClick = await _browser.EvaluateScriptAsPromiseAsync(scriptEventClick);
I have the need to move some processes to async. I have 5 methods that I need to call individually and have run in the background so the user can continue on with their work.
The test code below seems to work... but I haven't been able to figure out how to return information (message) indicating that the a task has completed. The class will be called from a separate windows form so that the progress can be displayed....
from the form:
async void BtnGo_Click(object sender, System.EventArgs e)
{
label2.Text = #"Starting tasks...";
var progress = new Progress<string>(
p =>
{
label2.Text = p;
});
await TestTask.MyTestMain(progress);
}
the class:
public static class TestTask
{
public static Task MyTestMain(IProgress<string> pProgress)
{
return SomethingAsync(pProgress);
}
private static async Task SomethingAsync(IProgress<string> pProgress)
{
var t1 = SomeThing1(pProgress);
var t2 = SomeThing2(pProgress);
await Task.WhenAll(t1, t2);
if (pProgress != null) pProgress.Report(#"all tasks completed");
}
private static async Task SomeThing1()
{
await Task.Delay(9000);
var filename = #"c:\temp\tt1.txt";
if (File.Exists(filename))
File.Delete(filename);
using (TextWriter tw = new StreamWriter(filename))
{
await tw.WriteLineAsync(DateTime.Now.ToLongDateString());
}
if (pProgress != null) pProgress.Report(#"t1 completed");
}
private static async Task SomeThing2()
{
await Task.Delay(7000);
var filename = #"c:\temp\tt2.txt";
if (File.Exists(filename))
File.Delete(filename);
using (TextWriter tw = new StreamWriter(filename))
{
await tw.WriteLineAsync(DateTime.Now.ToLongDateString());
}
if (pProgress != null) pProgress.Report(#"t2 completed");
}
}
I would like know when each task has completed. Any help or direction would be appreciated.
EDIT
I have edited this post to reflect my changes... I still cannot get a progress report back to the UI... any thoughts?
You're doing IO bound work, you don't need to use thread-pool threads.
Transform your methods to use the async APIs of StreamWriter:
private static async Task FirstThingAsync()
{
var filename = #"c:\temp\tt1.txt";
if (File.Exists(filename))
File.Delete(filename);
using (TextWriter tw = new StreamWriter(filename))
{
await tw.WriteLineAsync(DateTime.Now);
}
}
Same for your second method, and then you can asynchronously wait on them concurrently:
private static async Task SomethingAsync()
{
var firstThing = FirstThingAsync();
var secondThing = SecondThingAsync();
await Task.WhenAll(firstThing, secondThing);
}
Edit:
You're never reaching your first Progress.Report call because your code is throwing an InvalidOperationException when you call t.Start() on a promise-style task:
t1.Start();
await t1;
t2.Start();
await t2;
The task returned from both method calls is a "hot task", meaning it's operation is already started. The docs on Task.Start say:
InvalidOperationException: The Task is not in a valid state to be
started. It may have already been started, executed, or canceled, or
it may have been created in a manner that doesn't support direct
scheduling.
The reason you're not seeing that exception is because you're swallowing it:
var t = SomethingAsync(pProgress);
When you don't await on the async operation. Your method calls should look like this:
public static Task MyTestMain(IProgress<string> pProgress)
{
return SomethingAsync(pProgress);
}
async void BtnGo_Click(object sender, System.EventArgs e)
{
label2.Text = #"Starting tasks...";
var progress = new Progress<string>(
p =>
{
label2.Text = p;
});
await TestTask.MyTestMain(progress);
}
Given the following code, modified from Stephen Toub's article.
http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx
public async Task Start(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
await this.acceptCount.WaitAsync(token).ConfigureAwait(false);
if (token.IsCancellationRequested)
break;
var args = new SocketAsyncEventArgs();
args.UserToken = new SocketFrame
{
CancellationToken = token,
Buffer = null,
Message = null,
};
// How do I manage this additional task?
args.Completed += (s, e) => this.AcceptInbound((Socket)s, e).Wait(token);
if (!this.socket.AcceptAsync(args))
await this.AcceptInbound(this.socket, args);
}
}
private async Task AcceptInbound(Socket sender, SocketAsyncEventArgs e)
{
var frame = (SocketFrame)e.UserToken;
this.acceptCount.Release();
Socket connectedClient = e.AcceptSocket;
var args = new SocketAsyncEventArgs();
args.SetBuffer(new byte[0x1000], 0, 0x1000);
var awaitable = new SocketAwaitable(args);
while (!frame.CancellationToken.IsCancellationRequested)
{
await connectedClient.ReceiveAsync(awaitable);
var bytesRead = args.BytesTransferred;
if (bytesRead <= 0)
break;
if (this.AppendByteArrayToFrame(frame, args.Buffer, bytesRead))
this.reader.MessageReceived(frame.Message);
}
}
How do I avoid the Wait on the args.Completed event?
I want exceptions raised in AcceptInbound to bubble up, so I really don't want to wait there.
What I am trying to achieve is to bind the AcceptInbound task to the current task, so that when I wait on the current task, the exceptions raised are caught.
You can register an async event handler (which is the only place async void is appropriate). That allows you to await instead of blocking with Wait:
args.Completed += async (s, e) => await AcceptInbound((Socket)s, e);
If instead you want Start to handle completion and exceptions from all these tasks then I would store them all until Start completes and then use Task.WhenAll to make sure all operations completed and rethrow any exception they may produced:
public async Task Start(CancellationToken token)
{
var tasks = new ConcurrentBag<Task>();
while (!token.IsCancellationRequested)
{
// ...
args.Completed += (s, e) => tasks.Add(AcceptInbound((Socket)s, e));
// ...
}
await Task.WhenAll(tasks);
}
TaskCompletionSource to the rescue!
Take a look at this:
private TaskCompletionSource<Something> _tcs = new TaskCompletionSource<Something>();
public void FinishAwait(Something result) {
_tcs.SetResult(result);
}
public void FailAwait(Exception exception) {
_tcs.SetException(exception);
}
public async Task<Something> M() {
var result = await _tcs.Task;
return result;
}
This a very customised way of controlling when and how a particular Task completes.
You could use that to await a custom built task which is manually triggered by the completion of the AcceptAsync operation's event handler.
In your case, the generic argument T can be object because we only needed it to be void and object is the most benign of all types.
You could then await that intermediate task inside the Start method, just before awaiting the AcceptInbound method's Task.
Your problem was not that Exceptions are not passed from
callee to caller via await call but rather that you were
calling AcceptIncomming too soon.
Simply receiving true in the call to AcceptAsync doesn't mean that the "Accept occurred" but rather that is successfully started.
Take a look at that part of the code:
public async Task Start(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
await this.acceptCount.WaitAsync(token).ConfigureAwait(false);
if (token.IsCancellationRequested)
break;
var args = new SocketAsyncEventArgs();
args.UserToken = new SocketFrame
{
CancellationToken = token,
Buffer = null,
Message = null,
};
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
// How do I manage this additional task?
args.Completed += (s, e) => {
switch (e.SocketError) {
case SocketError.Success:
tcs.SetResult(null);
break;
default:
tcs.SetException(new SocketException());
break;
}
};
if (!this.socket.AcceptAsync(args)) {
// receiving a true value here
// only means that AcceptAsync
// started successfully
// we still need to
// "await for the Completed event handler"
await tcs.Task; // this will automatically throw
// if tcs.SetException(...) was used
// also, it will return null if all is well
// but most importantly, it will allow us to
// know when to call the next method
await this.AcceptInbound(this.socket, args);
}
}
}
this program reads a list of web site then saves them.
i found it runs good for the first 2 url requests. then goes very slow (about 5 min per request)
the time spend on row 1 and row 2 are only 2 second.
Then all other will be about 5 min each.
When i debug , i see it actually tooks long in wb.Navigate(url.ToString());
public static async Task<bool> test()
{
long totalCnt = rows.Count();
long procCnt = 0;
foreach (string url in rows)
{
procCnt++;
string webStr = load_WebStr(url).Result;
Console.WriteLine(DateTime.Now+ "["+procCnt + "/" + totalCnt+"] "+url);
}
return true;
}
public static async Task<string> load_WebStr(string url)
{
var tcs = new TaskCompletionSource<string>();
var thread = new Thread(() =>
{
EventHandler idleHandler = null;
idleHandler = async (s, e) =>
{
// handle Application.Idle just once
Application.Idle -= idleHandler;
// return to the message loop
await Task.Yield();
// and continue asynchronously
// propogate the result or exception
try
{
var result = await webBrowser_Async(url);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
// signal to exit the message loop
// Application.Run will exit at this point
Application.ExitThread();
};
// handle Application.Idle just once
// to make sure we're inside the message loop
// and SynchronizationContext has been correctly installed
Application.Idle += idleHandler;
Application.Run();
});
// set STA model for the new thread
thread.SetApartmentState(ApartmentState.STA);
// start the thread and await for the task
thread.Start();
try
{
return await tcs.Task;
}
finally
{
thread.Join();
}
}
public static async Task<string> webBrowser_Async(string url)
{
string result = "";
using (var wb = new WebBrowser())
{
wb.ScriptErrorsSuppressed = true;
TaskCompletionSource<bool> tcs = null;
WebBrowserDocumentCompletedEventHandler documentCompletedHandler = (s, e) =>
tcs.TrySetResult(true);
tcs = new TaskCompletionSource<bool>();
wb.DocumentCompleted += documentCompletedHandler;
try
{
wb.Navigate(url.ToString());
// await for DocumentCompleted
await tcs.Task;
}
catch
{
Console.WriteLine("BUG!");
}
finally
{
wb.DocumentCompleted -= documentCompletedHandler;
}
// the DOM is ready
result = wb.DocumentText;
}
return result;
}
I recognize a slightly modified version of the code I used to answer quite a few WebBrowser-related questions. Was it this one? It's always a good idea to include a link to the original source.
Anyhow, the major problem in how you're using it here is perhaps the fact that you create and destroy an instance of WebBrowser control for every URL from your list.
Instead, you should be re-using a single instance of WebBrowser (or a pool of WebBrowser objects). You can find both versions here.