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);
Related
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.
I am testing the validity of a large list of proxy servers concurrently. During this testing, many exceptions are being raised and caught. Although I am doing the testing in a background thread, my UI becomes unresponsive unless I use a SemaphoreSlim object to control the concurrency.
I know this is a self imposed bottle neck, and when scaling with an even larger list of proxies to test, I was hoping there might be a better way to solve the problem.
private void ValidateProxiesButton_Click(object sender, EventArgs e)
{
new Thread(async () =>
{
Thread.CurrentThread.IsBackground = true;
await ValidateProxiesAsync(proxies, judges, tests, 10);
}).Start();
}
public async Task ValidateProxiesAsync(IEnumerable<Proxy> proxies, IEnumerable<ProxyJudge> judges, IEnumerable<ProxyTest> tests = null, int maxConcurrency = 20)
{
if (proxies.Count() == 0)
{
throw new ArgumentException("Proxy list empty.");
}
foreach (var proxy in proxies)
{
proxy.Status = ProxyStatus.Queued;
}
//Get external IP to check if proxy is anonymous.
var publicIp = await WebUtility.GetPublicIP();
foreach (var judge in judges)
{
judge.Invalidation = publicIp;
}
await ValidateTestsAsync(judges.ToList<IProxyTest>());
var validJudges = judges.ToList<IProxyTest>().GetValidTests();
if (validJudges.Count == 0)
{
throw new ArgumentException("No valid judges found.");
}
if (tests != null)
{
await ValidateTestsAsync(tests.ToList<IProxyTest>());
}
var semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = new List<Task>();
foreach (var proxy in proxies)
{
tasks.Add(Task.Run(async () =>
{
await semaphore.WaitAsync();
proxy.Status = ProxyStatus.Testing;
var isValid = await proxy.TestValidityAsync((IProxyTest)validJudges.GetRandomItem());
proxy.Status = isValid ? ProxyStatus.Valid : ProxyStatus.Invalid;
semaphore.Release();
}));
}
await Task.WhenAll(tasks);
}
Inside proxy.TestValidityAsync method
public async Task<bool> TestValidityAsync(IProxyTest test, int timeoutSeconds = 30)
{
try
{
var req = WebRequest.Create(test.URL);
req.Proxy = new WebProxy(this.ToString());
var respBody = await WebUtility.GetResponseStringAsync(req).TimeoutAfter(new TimeSpan(0, 0, timeoutSeconds));
if (respBody.Contains(test.Validation))
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
return false;
}
}
So I found a working solution, it is to add the TPL Dataflow NuGet package to my project and then use the TransformBlock class. When I do this, my UI stays very responsive even if I am processing tons of concurrent requests that often throw exceptions. The code below is proof of concept, I will update it when I translate it to work with my project.
Source: Throttling asynchronous tasks
private async void button1_Click(object sender, EventArgs e)
{
var downloader = new TransformBlock<string, WebResponse>(
url => Download(url),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 200 }
);
var buffer = new BufferBlock<WebResponse>();
downloader.LinkTo(buffer);
var urls = new List<string>();
for (int i = 0; i < 100000; i++)
{
urls.Add($"http://example{i}.com");
}
foreach (var url in urls)
downloader.Post(url);
//or await downloader.SendAsync(url);
downloader.Complete();
await downloader.Completion;
IList<WebResponse> responses;
if (buffer.TryReceiveAll(out responses))
{
//process responses
}
}
private WebResponse Download(string url)
{
WebResponse resp = null;
try
{
var req = WebRequest.Create(url);
resp = req.GetResponse();
}
catch (Exception)
{
}
return resp;
}
}
I use Xamarin Auth to authenticate with OneDrive in my android app. I assume this would work, but I have the issue that when the activity for the login prompt is opened the programm will just continue and not wait for the auth to finish.
How can I wait until it's closed or wrap this in a other way async?
Code:
private IDictionary<string, string> authenticationResponseValues;
protected override async Task<AccountSession> GetAuthenticationResultAsync()
{
await Task.Run(() => ShowWebView());
return new AccountSession(authenticationResponseValues, this.ServiceInfo.AppId, AccountType.MicrosoftAccount)
{
CanSignOut = true
};
}
private void ShowWebView()
{
var auth = new OAuth2Authenticator(
clientId: MSA_CLIENT_ID,
scope: string.Join(",", scopes),
authorizeUrl: new Uri(GetAuthorizeUrl()),
redirectUrl: new Uri(RETURN_URL));
auth.Completed += SetAccountInfos;
var intent = auth.GetUI(Application.Context);
intent.SetFlags(ActivityFlags.NewTask);
Application.Context.StartActivity(intent);
}
private void SetAccountInfos(object sender, AuthenticatorCompletedEventArgs eventArgs)
{
if (eventArgs.IsAuthenticated)
{
Debug.WriteLine(eventArgs);
Debug.WriteLine(eventArgs.Account == null ? "IS NULL" : "IS NOT NULL");
if (eventArgs.Account != null)
{
OAuthErrorHandler.ThrowIfError(eventArgs.Account.Properties);
authenticationResponseValues = eventArgs.Account.Properties;
}
}
}
I dont think using async tactic is reasonable, because app runs before login result returns.
Try to use sync way. Make a login page. If success, then switch to your real app.
I found a solution. Here my code:
await ShowWebView();
return new AccountSession(authenticationResponseValues, ServiceInfo.AppId,
AccountType.MicrosoftAccount)
{
CanSignOut = true
};
private Task<bool> ShowWebView()
{
var tcs = new TaskCompletionSource<bool>();
var auth = new OAuth2Authenticator(OneDriveAuthenticationConstants.MSA_CLIENT_ID, string.Join(",", OneDriveAuthenticationConstants.Scopes), new Uri(GetAuthorizeUrl()),
new Uri(OneDriveAuthenticationConstants.RETURN_URL));
auth.Completed += (sender, eventArgs) =>
{
if (eventArgs.IsAuthenticated)
{
OAuthErrorHandler.ThrowIfError(eventArgs.Account.Properties);
authenticationResponseValues = eventArgs.Account.Properties;
tcs.SetResult(true);
}
};
var intent = auth.GetUI(Application.Context);
intent.SetFlags(ActivityFlags.NewTask);
Application.Context.StartActivity(intent);
return tcs.Task;
}
And the link to the class in the repo: https://github.com/Apply-Solutions/MoneyManager/blob/master/Src/MoneyManager.Droid/Src/AndroidAuthenticationProvider.cs
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.
I have this
public Result SomeMethod()
{
Popup popup = new Popup();
popup.Closed += PopupClosedHandler;
popup.ShowPopup();
// have to return the result from handler.
}
void PopupClosedHandler(EventArgs<PopupEventArgs> args)
{
Result result = args.Result;
}
I have to block SomeMethod() call until popup is called and return the Result from args in the handler. I have no idea how to do this or even how to look for it. Can anyone put me in the right direction? Thanks
You want to use an EventWaitHandle.
public Result SomeMethod()
{
_doneHandler = new EventWaitHandle(false, EventResetMode.ManualReset);
Popup popup = new Popup();
popup.Closed += PopupClosedHandler;
popup.ShowPopup();
// This will wait until it is SET. You can pass a TimeSpan
// so that you do not wait forever.
_doneHandler.WaitOne();
// Other stuff after the 'block'
}
private EventWaitHandle _doneHandler;
void PopupClosedHandler(EventArgs<PopupEventArgs> args)
{
Result result = args.Result;
_doneHandler.Set();
}
This is crude, but should give the general idea
public Result SomeMethod()
{
Popup popup = new Popup();
bool called = false;
Result result = null;
popup.Closed += (args) => {
called = true;
result = args.Result;
}
while(!called) ;
return result;
}