I have a webservice written in Yii (php framework).
I use C# and Visual Studio 2012 to develop a WP8 application. I added a service reference to my project (Add Service Reference). So I am able to use webservice functions.
client = new YChatWebService.WebServiceControllerPortTypeClient();
client.loginCompleted += client_loginCompleted; // this.token = e.Result;
client.loginAsync(this.username, this.password);
client.getTestCompleted += client_getTestCompleted;
client.getTestAsync(this.token);
function getTestAsync and loginAsync return void and both are asynchronous. Is it possible for the functions to return Task<T>? I would like to use async/await keywords in my program.
Assuming that loginAsync returns void, and loginCmpleted event fires when login is done, this is called the Event-based Asynchronous Pattern, or EAP.
To convert EAP to await/async, consult Tasks and the Event-based Asynchronous Pattern. In particular, you'll want to make use of the TaskCompletionSource to convert the event-based model to a Task-based model. Once you've got a Task-based model, you can use C# 5's sexy await feature.
Here's an example:
// Use LoginCompletedEventArgs, or whatever type you need out of the .loginCompleted event
// This is an extension method, and needs to be placed in a static class.
public static Task<LoginCompletedEventArgs> LoginAsyncTask(this YChatWebService.WebServiceControllerPortTypeClient client, string userName, string password)
{
var tcs = CreateSource<LoginCompletedEventArgs>(null);
client.loginCompleted += (sender, e) => TransferCompletion(tcs, e, () => e, null);
client.loginAsync(userName, password);
return tcs.Task;
}
private static TaskCompletionSource<T> CreateSource<T>(object state)
{
return new TaskCompletionSource<T>(
state, TaskCreationOptions.None);
}
private static void TransferCompletion<T>(
TaskCompletionSource<T> tcs, AsyncCompletedEventArgs e,
Func<T> getResult, Action unregisterHandler)
{
if (e.UserState == tcs)
{
if (e.Cancelled) tcs.TrySetCanceled();
else if (e.Error != null) tcs.TrySetException(e.Error);
else tcs.TrySetResult(getResult());
if (unregisterHandler != null) unregisterHandler();
}
}
Now that you've converted the Event-based async programming model to a Task-based one, you can now use await:
var client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask("myUserName", "myPassword");
I've had to do this a couple of times over the last year and I've used both #Judah's code above and the original example he has referenced but each time I've hit on the following problem with both: the async call works but doesn't complete. If I step through it I can see that it will enter the TransferCompletion method but the e.UserState == tcs will always be false.
It turns out that web service async methods like the OP's loginAsync have two signatures. The second accepts a userState parameter. The solution is to pass the TaskCompletionSource<T> object you created as this parameter. This way the e.UserState == tcs will return true.
In the OP, the e.UserState == tcs was removed to make the code work which is understandable - I was tempted too. But I believe this is there to ensure the correct event is completed.
The full code is:
public static Task<LoginCompletedEventArgs> RaiseInvoiceAsync(this Client client, string userName, string password)
{
var tcs = CreateSource<LoginCompletedEventArgs>();
LoginCompletedEventHandler handler = null;
handler = (sender, e) => TransferCompletion(tcs, e, () => e, () => client.LoginCompleted -= handler);
client.LoginCompleted += handler;
try
{
client.LoginAsync(userName, password, tcs);
}
catch
{
client.LoginCompleted -= handler;
tcs.TrySetCanceled();
throw;
}
return tcs.Task;
}
Alternatively, I believe there is a tcs.Task.AsyncState property too that will provide the userState. So you could do something like:
if (e.UserState == taskCompletionSource || e.UserState == taskCompletionSource?.Task.AsyncState)
{
if (e.Cancelled) taskCompletionSource.TrySetCanceled();
else if (e.Error != null) taskCompletionSource.TrySetException(e.Error);
else taskCompletionSource.TrySetResult(getResult());
unregisterHandler();
}
This was what I tried initially as it seemed a lighter approach and I could pass a Guid rather than the full TaskCompletionSource object. Stephen Cleary has a good write-up of the AsyncState if you're interested.
While adding your service reference make sure you selected Generate Task based operations in Advanced section. this will create awaitable methods like LoginAsync returning Task<string>
(Copied from OP, per https://meta.stackexchange.com/a/150228/136378 )
Answer:
Following code seems to work.
internal static class Extension
{
private static void TransferCompletion<T>(
TaskCompletionSource<T> tcs, System.ComponentModel.AsyncCompletedEventArgs e,
Func<T> getResult)
{
if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(getResult());
}
}
public static Task<loginCompletedEventArgs> LoginAsyncTask(
this YChatWebService.WebServiceControllerPortTypeClient client,
string userName, string password)
{
var tcs = new TaskCompletionSource<loginCompletedEventArgs>();
client.loginCompleted += (s, e) => TransferCompletion(tcs, e, () => e);
client.loginAsync(userName, password);
return tcs.Task;
}
}
I call it this way
client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask(this.username, this.password);
If you want to be able to await the methods, they should return Task. You cannot await a method that returns void. If you want them to return a value, like int they should return Task<int> then the method should return int.
public async Task loginAsync(string username, string password) {}
Then you can call
Task t = loginAsync(username, password);
//login executing
//do something while waiting
await t; //wait for login to complete
Related
Please forgive me for any noobish mistakes seen below, I'm learning some of the concepts I'm attempting to work with.
Problem:
While debugging my app, I was able to call an async function with Task.Start(). I felt that the app was in a working state for the phase I'm in so removed all breakpoints with CTRL + SHIFT + F9.
Once I ran the app with no breakpoints it would fail due to a property not getting populated. Now when I try to debug any breakpoint I set in the async function that handles most of the work is longer hit. It's like it is getting skipped. Can anyone see a reason why GetWowAuctionFileInfo isn't being called?
GetWowAuctionFileInfo is what is not getting called, or at least appears to be not getting called.
Thanks.
Relevant Code
Caller Function
private void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
Called Functions
public void StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
Task t = new Task(() => GetWowAuctionFileInfo(optionalUri));
t.Start();
//Func<string> function = new Func<string>(() => GetWowAuctionFileInfo(optionalUri));
//Task<string> tInfo = Task<string>.Factory.StartNew(() => GetWowAuctionFileInfo(optionalUri));
}
}
}
private async void GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
returnValue = ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
ReturnedData = returnValue;
}
private List<string> ConvertFileInfoToConsumableList(RealmJSFileCheck jsfc)
{
List<string> returnData = new List<string>();
if (jsfc.files.Count > 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("File URL: ");
sb.Append(jsfc.files[0].url);
returnData.Add(sb.ToString());
sb = new StringBuilder();
sb.AppendLine("Last Modified: ");
sb.Append(jsfc.files[0].lastModified);
returnData.Add(sb.ToString());
}
else
{
returnData.Add("No File Info Found");
}
return returnData;
}
UPDATE
Thanks again all for the detailed commentary. I've gone through much documentation regarding Task usage and learned a lot in this exercise. I'm marking the answer from #Johnathon as the solution because it provided exactly what I was asking for and provided a very helpful link for more information.
Your GetWowAuctionFileInfo method is an asynchronous method, and you await an async call within it without returning a Task. In general it is bad practice to use async void. Instead, turn your GetWowAuctionFileInfo method into async Task<List<string>> GetWowAuctionFileInfo. This will let you await the GetAsync call, parse the data, and return the collection to the caller without having to use a ReturnObject.
private async Task<List<string>> GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
// You can just return the List<T> now.
return ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
}
Because the method was originally async void, you could not await the calling of it in your buttonTestJSFCHI_Click. Now that we've made it all Task based, you can await it within your event handler. Note that event handlers are generally the only acceptable place to use async void. Any time you are responsible for the creation of the methods, and not constrained by a contract (like event handlers), you should always return a Task on your async methods.
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
List<string> results = await w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in results)
{
textBoxResults.Text += res;
}
}
public async Task<List<string>> StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
// Since the GetWowAuctionFileInfo now returns Task, we don't need to create a new one. Just await the Task given back to us, and return the given result.
return await GetWowAuctionFileInfo(optionalUri);
}
}
}
The reason you saw the expected result while debugging is because the debug session was slow enough that the async operation completed in time for your code to use it. When running the app outside of the debugger, it runs faster than the async operation could complete, preventing you from seeing the data. Thus the need to await the entire async call stack, so you can prevent further execution from happening down that code-path until you receive all of the desired data.
Microsoft has a good write up on Task based programming, I'd take a read through it to help you understand it some.
EDIT
Just to clarify, when you return a Task<T> on your methods, you will be given the result when you await. For example:
List<string> result = await StartTask();
Even though StartTask returns Task<List<string>>, the await operation will wait for the StartTask() method to complete, and then unwrap the result from the Task<T> object and give you the result back automatically. So don't let the method signature fool you, if you await it, you will be given back the resulting data, and not the actual Task itself. There won't be any need for you to pull the data out of the Task manually.
Because you not waiting for result.
You loop with ReturnedData before it was assigned with data.
I think you don't need to create new Task at all. Make GetWowAuctionFileInfo method properly asynchronous which returns Task.
private async Task GetWowAuctionFileInfo(string auctionInfoUri)
{
// same code
}
Change StartTask to return Task. Because we not awaiting result here we don't need make method asynchronous.
Suggest to change name of this method to LoadData for example, which give more information about what this method does.
public Task StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
return GetWowAuctionFileInfo(optionalUri) // this will return Task
}
}
// if validation fails - return completed task or throw exception
return Task.CompletedTask;
}
Then you can call it in Button_Click event handler
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
await w.StartTask("FileInfo", "yourUrl");
// This line will be executed only after asynchronous methods completes succesfully
// or exception will be thrown
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
I have an interesting problem.. I have a login method which works a WCF service.
I created a taskcompletion and waits until result is come.
Well problem is, if I call 2 times login method, the second one does not return anything. I put break point and it enters the completed event and it calls trysetresult but nothing return.
here is my code
public Task<User> LoginByUserName(string userName, string password)
{
var tcs = new TaskCompletionSource<User>();
if (!_registeredEventList.Contains ("LoginByUserNameCompleted")) {
_registeredEventList.Add ("LoginByUserNameCompleted");
userService.LoginByUserNameCompleted += (object sender, LoginByUserNameCompletedEventArgs args) => {
if (args.Error != null)
tcs.TrySetException (args.Error);
if (args.Result != null)
tcs.TrySetResult (args.Result);
else
tcs.TrySetResult (null);
};
}
userService.LoginByUserNameAsync (userName,password);
return tcs.Task;
}
I call like that;
var loginResult= await Task.Run(()=>serviceHelper.LoginByUserName(userName,password));
For example, if user one time entered wrong login info, in the second try, nothing will return.
PS: _registeredEventList holds if event is already subscribed or not. If yes then it does not creat again. When I delete that part, it works.
As Evk commented, the problem is that your code has a condition where it will never return the completed task. Specifically, the first time this code is called, it will add an entry to _registeredEventList (which is presumably never removed). All later invocations will return a Task that is never completed, which is a major no-no in asynchronous programming.
To fix this, I recommend modifying your EAP wrapper by unsubscribing as part of the callback:
public static Task<User> LoginByUserNameTaskAsync(this UserService #this, string userName, string password)
{
var tcs = new TaskCompletionSource<User>();
LoginByUserNameCompletedDelegate callback = null;
callback = (object sender, LoginByUserNameCompletedEventArgs args) =>
{
#this.LoginByUserNameCompleted -= callback;
if (args.Error != null)
tcs.TrySetException(args.Error);
else
tcs.TrySetResult(args.Result);
};
#this.LoginByUserNameCompleted += callback;
#this.LoginByUserNameAsync(userName, password);
return tcs.Task;
}
(I also made it an extension method and had it follow the TAP naming parameters for TAP-over-EAP wrappers).
I have just solved the same problem. For me its was not inheriting IDisposable and adding the following
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed && disposing)
{
_channel?.Dispose();
_connection?.Dispose();
}
this.disposed = true;
}
These two functions work but my problem is that the anonymous function (the one that receives the ServiceCheck as argument) is never called.
What do I have to do to make the CheckServiceConnection function return the string so that the anonymous function will run?
internal async void CheckServiceConnection()
{
await _da.CheckServiceConnection((ServiceCheck) =>
{
GeneralEventArgs args = new GeneralEventArgs();
args.GeneralObject = (object)ServiceCheck;
ServiceConnection(this, args);
});
}
public Task<string> CheckServiceConnection(Action<string> OnComplited)
{
var tcs = new TaskCompletionSource<string>();
ws.CheckServiceCompleted += (s, e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else if (e.Result != null)
tcs.TrySetResult(e.Result);
};
try
{
ws.CheckServiceAsync();
}
catch (Exception ex)
{
ws.LogAsync(ex.Message, "DataManager.CheckServiceConnection()");
}
return tcs.Task;
}
When I write the CheckServiceConnection function like that - it is working
(and of course that CheckServiceConnection receives no arguments):
internal async void CheckServiceConnection()
{
var ServiceCheck = await _da.CheckServiceConnection();
GeneralEventArgs args = new GeneralEventArgs();
args.GeneralObject = (object)ServiceCheck;
ServiceConnection(this, args);
}
Your CheckServiceConnection method never calls the OnComplited action argument passed to it. That's why your anonymous function in the first function never gets called.
If you want to pass an anonymous function to CheckServiceConnection and have it called, you need to call it. That is, you add a line like this:
OnComplited( "some string" );
You're never calling the argument. It's not going to be called automatically.
However, I don't see much of a reason to do this anyway. You're awaiting the method, so why not just do this?
internal async void CheckServiceConnection()
{
var result = await _da.CheckServiceConnection();
GeneralEventArgs args = new GeneralEventArgs();
args.GeneralObject = result;
ServiceConnection(this, args);
}
Also, are you sure the CheckServiceConnection method shouldn't return Task? That's rarely a good idea.
All in all, it seems like you're mixing a lot of different approaches to notifications, asynchronous code etc. Maybe you want to revise your design a bit?
_fbClient.GetCompleted += new EventHandler<FacebookApiEventArgs>(OnFetchPageNotification);
_fbClient.GetAsync(_kNotificationPath, new Dictionary<string, object> { { "access_token", _kPageAccessToken } });
How to convert above code into awaitable code in wp7:
object = await _fbClient.GetAsync(_kNotificationPath, new Dictionary<string, object> { { "access_token", _kPageAccessToken } });
I have CTP Installed and task parallel library also.
The Async CTP came with a document that describes how to adapt each existing pattern to the Task Based Async pattern. It says that the Event based one is more variable, but does give one example:
public static Task<string> DownloadStringAsync(Uri url)
{
var tcs = new TaskCompletionSource<string>();
var wc = new WebClient();
wc.DownloadStringCompleted += (s,e) =>
{
if (e.Error != null) tcs.TrySetException(e.Error);
else if (e.Cancelled) tcs.TrySetCanceled();
else tcs.TrySetResult(e.Result);
};
wc.DownloadStringAsync(url);
return tcs.Task;
}
Where the original function that's being wrapped is DownloadStringAsync, the parameters match the parameters being passed to this function, and DownloadStringCompleted is the event that is being monitored.
(The same document appears to be downloadable here - the above sample (and more description) are from "Tasks and the Event-based Asynchronous Pattern (EAP)")
From what I've seen about using the Async CTP with the event asynchronous pattern, the code I have here should work fine, with var result1 = await tcs1.Task blocking until clientGetFileList.GetCompleted fires. However, what ends up happening is that I get bounced back to GetRestoreStream, on return GetRestoreStreamAwait().Result which never does return -- instead, my app pretty much locks up on me.
Can someone please explain to me what it is I am doing wrong?
protected override Stream GetRestoreStream()
{
if (SkyDriveFolderId != null)
return GetRestoreStreamAwait().Result;
return Stream.Null;
}
private async Task<Stream> GetRestoreStreamAwait()
{
LiveConnectClient clientGetFileList = new LiveConnectClient(_session);
TaskCompletionSource<LiveOperationCompletedEventArgs> tcs1 = new TaskCompletionSource<LiveOperationCompletedEventArgs>();
EventHandler<LiveOperationCompletedEventArgs> d1 = (o, e) => { tcs1.TrySetResult(e); };
clientGetFileList.GetCompleted += d1;
clientGetFileList.GetAsync(SkyDriveFolderId + "/files");
var result1 = await tcs1.Task;
clientGetFileList.GetCompleted -= d1;
// ... method continues for a while
}
Update: This bit of code seems to move through all the way, but task.Start() tosses off an InvalidOperationException so I never actually get the stream at the end. Wrapping it in a try/catch doesn't change anything, either -- without the try/catch the InvalidOperationException is caught further up the stack while the operation runs happily ignorant of the fact its result will never be used; with it, task.Result freezes things just as surely as the code above.
protected override Stream GetRestoreStream()
{
if (SkyDriveFolderId != null)
{
var task = GetRestoreStreamImpl();
task.Start();
return task.Result;
}
return Stream.Null;
}
private async Task<Stream> GetRestoreStreamImpl()
{
var getResult = await GetTaskAsync(SkyDriveFolderId + "/files");
List<object> data = (List<object>)getResult["data"];
foreach (IDictionary<string, object> dictionary in data)
{
if (dictionary.ContainsKey("name") && (string)dictionary["name"] == BackupFileName)
{
if (dictionary.ContainsKey("id"))
{
SkyDriveFileId = (string)dictionary["id"];
break;
}
}
}
if (String.IsNullOrEmpty(SkyDriveFileId))
{
MessageBox.Show("Restore failed: could not find backup file", "Backup", MessageBoxButton.OK);
return Stream.Null;
}
return await DownloadTaskAsync(SkyDriveFileId + "/content");
}
private Task<IDictionary<string,object>> GetTaskAsync(string path)
{
var client = new LiveConnectClient(_session);
var tcs = new TaskCompletionSource<IDictionary<string, object>>();
client.GetCompleted += (o, e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
client.GetAsync(path);
return tcs.Task;
}
private Task<Stream> DownloadTaskAsync(string path)
{
var client = new LiveConnectClient(_session);
var tcs = new TaskCompletionSource<Stream>();
client.DownloadCompleted += (o, e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
client.DownloadAsync(path);
return tcs.Task;
}
You are misunderstanding the way that async/await works. Basically, your code is blocking at the var result1 and below. However, what await allows is for the code that called the async method (GetRestoreStream in this case) to be returned to as soon as a long running task* with await in front of it is called. If you were not reliant on the .Result, then your GetRestoreStream method would complete. However, since you require the result, then your GetRestoreStream method becomes synchronous while it waits for GetRestoreStreamAwait to complete. I will add some visuals shortly.
Here is some sample code flow:
-GetRestoreStream calls GetRestoreStreamAwait
---GetRestoreStreamAwait calls an async task
-GetRestoreStreamAwait returns to GetRestoreStream with a pending result
-GetRestoreStream can do anything it wants, but if it calls for the pending result, it will block
---GetRestoreStreamAwait finally finishes its async task and continues through its code, returning a result
-Any code in GetRestoreStream that was waiting for the result receives the Result
This is not the best graphical representation, hopefully it helps explain it a little though. The thing to notice is that the code flow is not what you are used to due to the nature of async
So, my guess is that your app locks up only because you are trying to access the Result that is not available yet, and all you should have to do is wait for the tcs1.Task to complete. If you want to avoid locking up, then you will need to nest the call so that GetRestoreStream is also an async method. However, if the result is what you are ultimately looking for, then you will need to wait for the return, or simply set up a callback like you normally would for the async pattern
*Notice that I said long running task because the compiler will not waste time rewriting code that is already completed (if it is indeed completed by the time the await is called)
UPDATE...try this
protected override Stream GetRestoreStream()
{
if (SkyDriveFolderId != null)
return GetRestoreStreamAwait().Result;
return Stream.Null;
}
private async Task<Stream> GetRestoreStreamAwait()
{
try
{
LiveConnectClient clientGetFileList = new LiveConnectClient(_session);
TaskCompletionSource<LiveOperationCompletedEventArgs> tcs1 = new TaskCompletionSource<LiveOperationCompletedEventArgs>();
EventHandler<LiveOperationCompletedEventArgs> d1 =
(o, e) =>
{
try
{
tcs1.TrySetResult(e);
}
catch(Exception ex)
{
tcs1.TrySetResult(null);
}
};
clientGetFileList.GetCompleted += d1;
clientGetFileList.GetAsync(SkyDriveFolderId + "/files");
var result1 = await tcs1.Task;
clientGetFileList.GetCompleted -= d1;
// ... method continues for a while
}
catch(Exception ex)
{
return null;
}
}