InvalidOperationException when do a process twice
Asked Answered
N

2

7

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?

Nattie answered 10/5, 2014 at 3:43 Comment(3)
Did you actually wait for the method to execute or clicked on the login button multiple times?Accidence
I did wait til the process is done then I clicked to do it again. I think the process need to be stopped before start it again. But I don't know how to stop it.Nattie
The task should complete before you hit the code again. You don't need to manually stop it. See how Jon Skeet solution posted in your question https://mcmap.net/q/1623109/-tasks-seem-to-start-automaticallyAccidence
N
6

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

Nattie answered 4/8, 2015 at 6:36 Comment(0)
R
1

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;
Rinna answered 20/6, 2015 at 9:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.