We have a WPF application using WebView2 where one of the flows need a call originating from the WPF app to call a JS function asynchronously. The JS function in turn will call a C# method in the Host Object Proxy class which would eventually need to call another JS function but this time the call would need to be synchronous. So the call flow is something like below:
C# code calls JS function async
JS function calls C# code in host
object proxy Host object proxy method in C# would call a JS function
synchronously
A sample workflow could be a Save button in the WPF app which calls some JS function which calls back to a C# method defined in the host object proxy which needs to do a dirty check in the JS layer and hence calls another JS function. The last JS function call needs to be synchronous as it is existing code where async/await pattern introduction will lead to lot of changes.
The method that we have been using to convert a asynchronous script execution call to synchronous in the WPF app is as below:
public static T WaitWithMessagePumping<T>(Task<T> asyncAction)
{
DispatcherFrame frame = new DispatcherFrame();
asyncAction.ContinueWith(t => frame.Continue = false);
Dispatcher.PushFrame(frame);
return asyncAction.GetAwaiter().GetResult();
}
This has worked well for calls which are originating from the WPF side.
But since the call in question is actually being invoked from JS (we can see a native to managed transition in the call stack), the applications stops responding when the code reaches the Dispatcher.PushFrame(frame) API call (seems to go into deadlock).
So essentially the issue is how do we call a script synchronously from C# when the call is originating in the context of a JS function calling a host object method?
simple project link which replicates the situation: Gitlab url
This has worked for me,
if your host object has a :
public void DoStuffAsync()
{
var tmp = WaitWithMessagePumping(webView2.ExecuteScriptAsync("box1.value = '123';");
}
Instead do this :
public void DoStuffAsync()
{
webView2.Dispatcher.InvokeAsync(() =>
{ var tmp = WaitWithMessagePumping(webView2.ExecuteScript("box1.value = '123'")));};
}
It works fine.
Related
I have access to WSDL file of a specific .asmx web service that contains a SendDataAsync method - basically I specify the TimeStamp and Value to be send. I uploaded the WSDL file to my project in Visual Studio 2019 as a connected service (Add->Connected Service->Microsoft WCF Web Service Reference Provider->Browse->I added location of the WSDL file and specified the service that included SendDataAsync method). After that I created a new client and tried to use my method like that:
ServiceSoapClient client = new ServiceSoapClient(ServiceSoapClient.EndpointConfiguration.ServiceSoap);
client.SampleData sd = new client.SampleData();
sd.TStamp = DateTime.Now;
sd.Value = 10;
client.SendDataAsync(sd);
Unfortunately, it doesn't work. I don't receive any errors or exceptions so I tried to check the response from the web service via Fiddler. I found out that actually nothing is being transmitted. No connection is being made, nothing. Now I try to understand what I'm doing wrong. Is my way of using the method defined on the web service wrong? Or maybe the method doesn't actually do what the name suggests? Or could the problem be related to the fact that the method is Async? Any suggestions are welcome! :)
According your description,I made a demo.The asynchronous method in demo is automatically generated by the client-side according to SendData, that is, the server-side has no SendDataAsync method, and the server-side only has SendData.
public void SendData(SampleData data)
{
Console.WriteLine(data.TStamp);
Console.WriteLine(data.value);
Console.WriteLine("success");
}
This is the SendData method of the server-side.
public System.Threading.Tasks.Task SendDataAsync(Client_SOAP.ServiceReference1.SampleData data) {
return base.Channel.SendDataAsync(data);
}
This is an asynchronous method automatically generated by the client-side.
ServiceReference1.Service1Client service1Client = new Service1Client();
SampleData sampleData = new SampleData();
sampleData.value = 10;
sampleData.TStamp = DateTime.Now;
service1Client.SendDataAsync(sampleData);
service1Client.Close();
Console.ReadLine();
This is the client-side calling asynchronous methods.
This is the execution result of the server-side after the client-side calls.
In another case,if your asynchronous method is implemented by the server-side, there are three ways for the server to implement asynchronous operation: the task-based asynchronous pattern, the Event-based Asynchronous Pattern, the IAsyncResult asynchronous pattern.For the different asynchronous model used by the server-side, the client calls in different ways.
The following link details the asynchronous invocation of the client-side:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/synchronous-and-asynchronous-operations
Okay, thanks to both the comment from Paulo Morgado and the answer from Ding peng I managed to solve my problem. The proper way of using asynchronous method, such as SendDataAsync in my case, is with the await operator. I changed the method invocation from:
client.SendDataAsync(sd);
to:
response = await client.SendDataAsync(sd);
I also had to change void Main to async Main and the method works now :)
As part of the project we have implemented ASP.Net Web API, which returns the Json data, which is consumed by the Javascript using Angular JS on the client.
Controller code is straight forward (Trimmed description):
public class CardController : ApiController
{
// code
[HttpGet]
public CardDataGetUI GetCardDataUI(int userID, int dashBoardID, int cardID)
{
// Access the application Cache object using HttpRuntime (System.Web.Caching)
var blCache = HttpRuntime.Cache;
// Create a user specific BL access key by concatenating the user ID
string userBLAccessKey = WebAPIConstant.BlUserDashboardCard + userID;
// Access the BL object stored in the Cache
accessBL = (Bl)blCache[userBLAccessKey];
// Other Code
// Fetch the data for the control being passed
cardDataUI = accessBL.GetCardDataUI(dashBoardID, cardID);
return (cardDataUI)
}
}
The above mentioned GetCardDataUI delivers the card data for different type of control like chart, map and grid on a same UI screen, so what UI does is make an Asynchronous call to all in one go, currently I have BL (business layer) object being accessed from application wide cache, which is an issue for Multi threaded access, as they would share same object, so I have converted that to a local copy and initialized the one for each call to the controller. However that is also good enough till the each ajax call is having it's unique controller instance to call the method. However in this case it seems the http call they make have same instance thus modifying the input variable of each call thus leading to unexpected result and exception, since it is modifying the internal DS at run time. It is akin to calling the static method
Ideally I did not expected a multi-threaded call to the business layer, but it seems in Angular JS client has to make such calls, they cannot be synchronous.
Currently I have resolved the situation by introducing a lock in the controller, which certainly allows one thread at a time
However was looking for a solution like each Ajax call can have it's own controller instance, when it make the http get call.
We also have an option of modifying the above mentioned controller method like:
public CardDataGetUI[] GetCardDataUI(int userID, int dashBoardID, int[] cardID)
{
// Code
}
In this case there will be one call for all cards and I will call the data fetch in a for loop, thus synchronizing the operation, but this is not much different from locking the controller, preferable will be a separate controller instance for each AJAX call
Any suggestion?
I was working on an application that makes quite a few requests using the HttpWebRequest class to bring back data from the web. Now my app makes nearly identical calls in multiple parts of my application. This is turning into a problem because I'm duplicating a lot of code.
The ideal solution would be to encapsulate the logic that makes the calls to the web in it's own class that way updating is a breeze and it can be reused throughout my application. I'm just not sure how to make an asynchronous call in a separate class and return the value to my main code behind so I can update the UI with the data.
Can someone provide me with some guidance on how to make this happen? I know this has to be possible because developers are doing it all the time when following the MVVM pattern.
I'm using Silverlight/C#.
We have code like this in our Silverlight app for use with WCF Data Services. You could probably do something similar for your web requests:
Here is some sample code (untested) (note that I have not actually ever written any code that does web requests, but maybe the async pattern is similar to other stuff that I have done in Silverlight):
public class WebRequesterHelper
{
Action _callback;
public void MakeWebRequest(object whateverYouNeedForTheWebRequest, Action callback)
{
_callback = callback;
//Make your async web request here, passing the helper object's callback.
IAsyncResult result = yourWebRequestObject.BeginGetResponse(new AsyncResultCallback(WebRequestCallback), yourRequestState);
}
public void WebRequestCallback(IAsyncResult result)
{
//Do whatever you need to do as a result of the web request, then call the callback.
if (_callback != null) callback();
}
}
In your code that wants to make a web request:
var helper = new WebRequestHelper();
//Setup the web request
object request = SetUpYourWebRequest();
helper.MakeWebRequest(request, OnWebRequestCompleted);
Your helper callback:
public void OnWebRequestCompleted()
{
//Web request is finished, what do I want to do?
}
Note that you might want your callback (which you pass to the WebRequestHelper object) to accept a parameter and you could pass information back out from the web request callback function (in the WebRequestHelper object).
Since your web download code is effectively your business logic I would definitely encapsulate it in it's own class as you planned. Then you should use events to notify your UI of state changes.
For example, your "WebRequestManager" instance can encapsulate all the thread handling and will raise events to allow the UI to respond accordingly. You would raise an event on TaskCompleted and perhaps also at intervals to provide looking feedback such as AsyncProgressChanged.
NOTE: By way of convention, I would tend to prefix any events that are likely going to be called from a thread other than the original calling one with the name "Async" so that the UI handler knows to join back onto the UI thread.
We have a service to update customer information to server. One service call takes around few seconds, which is normal.
Now we have a new page where at one instance around 35-50 Costumers information can be updated. Changing service interface to accept all customers together is out of question at this point.
I need to call a method (say "ProcessCustomerInfo"), which will loop through customers information and call web service 35-50 times. Calling service asynchronously is not of much use.
I need to call the method "ProcessCustomerInfo" asynchronously. I am trying to use RegisterAsyncTask for this. There are various examples available on web, but the problem is after initiating this call if I move away from this page, the processing stops.
Is it possible to implement Fire and Forget method call so that user can move away (Redirect to another page) from the page without stopping method processing?
Details on: http://www.codeproject.com/KB/cs/AsyncMethodInvocation.aspx
Basically you can create a delegate which points to the method you want to run asynchronously and then kick it off with BeginInvoke.
// Declare the delegate - name it whatever you would like
public delegate void ProcessCustomerInfoDelegate();
// Instantiate the delegate and kick it off with BeginInvoke
ProcessCustomerInfoDelegate d = new ProcessCustomerInfoDelegate(ProcessCustomerInfo);
simpleDelegate.BeginInvoke(null, null);
// The method which will run Asynchronously
void ProcessCustomerInfo()
{
// this is where you can call your webservice 50 times
}
This was something I whipped just to do that...
public class DoAsAsync
{
private Action action;
private bool ended;
public DoAsAsync(Action action)
{
this.action = action;
}
public void Execute()
{
action.BeginInvoke(new AsyncCallback(End), null);
}
private void End(IAsyncResult result)
{
if (ended)
return;
try
{
((Action)((AsyncResult)result).AsyncDelegate).EndInvoke(result);
}
catch
{
/* do something */
}
finally
{
ended = true;
}
}
}
And then
new DoAsAsync(ProcessCustomerInfo).Execute();
Also need to set the Async property in the Page directive <%# Page Async="true" %>
I'm not sure exactly how reliable this is, however it did work for what I needed it for. Wrote this maybe a year ago.
I believe the issue is the fact is your web service is expecting a client to return the response to, that the service call itself is not a one way communication.
If you're using WCF for your webservices look at http://moustafa-arafa.blogspot.com/2007/08/oneway-operation-in-wcf.html for making a one way service call.
My two cents: IMO whoever put the construct on you that you're not able to alter the service interface to add a new service method is the one making unreasonable demands. Even if your service is a publicly consumed API adding a new service method shouldn't impact any existing consumers.
Sure you can.
I think what you are wanting is a true background thread:
Safely running background threads in ASP.NET 2.0
Creating a Background Thread to Log IP Information
I have a process where an incoming user request to our system is being handled. I also want to add some metadata about the request to a database table without impacting the responsiveness of the main process. To achieve this I added a call to an asynchronous method like this:
public static ReturnObject ResponsiveMethod(string ip, string code)
{
// ... some reasonably quick code
IPDetail.InsertAsync(ip); // <-- call to the async method
return new ReturnObject(code);
}
The InsertAsync() method looks like this:
public static void InsertAsync(string ipAddress)
{
Action action = () => IPDetail.Insert(ipAddress);
action.BeginInvoke(aResult => Log.Debug("Completed Insert"), null);
}
And finally, the normally non-asynchronous method called Insert():
private static void Insert(string ipAddress)
{
ApplicationContextHelper.LoadApplicationContext();
var helper = new GeoLocationHelper();
var result = helper.GetDetailsFromIP(ipAddress);
Log.InfoFormat("Succesfully retreived IP data {0}.", ipAddress);
result.Save();
}
In my unit tests the InsertAsync() call works perfectly. Inside the method calls in Insert() there are many operations occuring which are detailed by logging, and all the expected log messages are there, as well as the final result of the result.Save() method.
However, we have a webservice which utilizes something like the ResponsiveMethod() method above and for some reason the asynchronous calls do not complete. All of the logging in the LoadApplicationContext() method gets fired, but after that there is no log activity related to the Insert() and the result.Save() is never getting executed.
Revised summary question to be more concise
My current thinking is that the webservice has completed its task and the thread which called the asynchronous no longer exists. Would this stop the async call from completing?
I've never used BeginInvoke before, but usually where there's a Begin*, you also need the coresponding End*. Please add one, along with correct exception handling.
My first thought is that you may be throwing an exception on your async call in the web service scenario for some reason. I know you've probably pared it down to post it on the web, but is there any "more-or-less unfailable" error handling code in there?
Are you relying on the identity of the caller in the Async method call? The identity may be lost when called from the web service.