Question

I had to write an async method to contact with web service. This is my method in WebServiceHelper class:

 public static Task<int> SignIn(string username, string password) 
    {

        try
        {
            TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
            service.LoginCompleted += (object sender, WebService.LoginCompletedEventArgs e) => 
            {
                if (e.Error != null) tcs.SetResult(-1);
                else
                    tcs.SetResult((int)e.Result);

            };
            service.LoginAsync(username, password);
            return tcs.Task;
        }
        catch (Exception ex)
        {

            MessageBox.Show("Error: " + ex.Message);
            return null;
        }

    }

Then I call it in a button clicked event like this:

private async void btLogIn_Click(object sender, RoutedEventArgs e)
    {                       
        try
        {
            int si = await WebServiceHelper .SignIn(tbUsername.Text, tbPassword.Text);               
            if (si != 0) MessageBox.Show("Signed in successfully!");
            else MessageBox.Show("Couldn't sign in");
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }            
    }

It worked fine on the first time I clicked the button, but when I did in again, an error came up: "InvalidOperationException, An attempt was made to transition a task to a final state when it had already completed."

I did some little search and found something here: Tasks seem to start automatically

I understand that I should do something to stop the process before start it again but I don't know exactly how and why. Can someone please explain this for me?

Was it helpful?

Solution

I tried to use the method TrySetResult() instead of SetResult() and it worked!

OTHER TIPS

I suspect the problem is that you're not unregistering your event handler and every time you make a call to this method you're adding a new anonymous event handler to service.LoginCompleted

Try this

public static Task<int> SignIn( string username, string password )
{
   TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
   EventHandler<WebService.LoginCompletedEventArgs> onLoginCompleted = null;
   onLoginCompleted = ( object sender, WebService.LoginCompletedEventArgs e ) =>
   {
      service.LoginCompleted += onLoginCompleted;
      if(e.Error != null)
      {
         tcs.SetResult( -1 );
      }
      else
      {
         tcs.SetResult( (int)e.Result );
      }
   };
   service.LoginCompleted += onLoginCompleted;
   service.LoginAsync( username, password );
   return tcs.Task;
}

or perhaps this

public static Task<int> SignIn( string username, string password )
{
   TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
   EventHandler<WebService.LoginCompletedEventArgs> onLoginCompleted = ( object sender, WebService.LoginCompletedEventArgs e ) =>
   {
      if(e.Error != null)
      {
         tcs.SetResult( -1 );
      }
      else
      {
         tcs.SetResult( (int)e.Result );
      }
   };
   service.LoginCompleted += onLoginCompleted;
   tcs.Task.ContinueWith(task => service.LoginCompleted -= onLoginCompleted);
   service.LoginAsync( username, password );
   return tcs.Task;
}

As an aside, you should also remove that universal try/catch around the method and return tcs.Task in all situations.

If it is actually likely that service.LoginAsync( username, password ) could throw an exception then you should do this

...
try
{
   service.LoginAsync( username, password );
}
catch(SomeParticularException ex)
{
   tcs.SetException(ex);
}
return tcs.Task;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top