需要同步时导致死锁的异步事件

时间:2014-08-05 13:49:32

标签: c# user-interface asynchronous async-await deadlock

我创建了一个使用REST API的类。我写了这个类与Web服务异步通信,因为我原本不认为我需要运行同步的任何东西。现在我遇到一种情况,我意识到使用异步方法对于我的应用程序中的某个特定情况并不理想,因为它运行失序并导致异常,因为应用程序试图调用它尚未准备好的方法。我不是百分之百确定为什么会发生这种情况,但我认为这是因为我的UI中的async void事件中调用了这些方法。以下是一些显示情况示例的代码段:

class MyForm : Form
{
     private RestConnection connection;         
     private async void MyForm_Load(object sender, EventArgs e)
     {
          if(connection == null)
          using (LogOnDialog logOnDialog = new LogOnDialog())
          {
              var result = logOnDialog.ShowDialog(this);
              if(result == DialogResult.OK)
              {
                  connection = logOnDialog.Connection;
               }
           }

           formComboBox.DataSource = await connection.GetChoices();
     }
}

class LogOnDialog : Form
{
    public RestConnection Connection {private set;get;}
    private async void saveButton_Click(object sender, EventArgs e)
    {
          RestConnection conn = new RestConnection(userNameTB.Text, passwordTb.Text);
          await conn.LogIn();
          if(conn.LoggedIn) //issue here
          {
                Connection = conn;
                DialogResult = DialogResult.OK;
                this.Close();
           }
          else
          {
              Connection = null;
              DialogResult = DialogResult.Abort;
              MessageBox.Show("Invalid Credentials, Try Again.");
           }
      }
}

正在发生的是应用程序正在尝试调用connection.GetOptions(),但是连接仍然为null,因为LogOnDialog的异步事件创建了连接并检查是否成功在允许向呼叫者提供连接之前登录。但是,由于连接为空,因为Click事件尚未完成,因此调用NullReferenceException。另外,如果我继续过去并忽略异常,则抛出ObjectDisposedException,因为我们现在在using块之外。

我试图通过从事件中删除async关键字并在登录方法上调用Wait()来强制登录同步。这导致了僵局。我还尝试使用以下代码捕获任务,并使用spinwait:

Task t = conn.LogOn();
while(!t.IsCompleted)
    Thread.Sleep(50);

这并没有陷入僵局,但它确实永远旋转了。每次我在While条件下检查断点时,任务的状态始终是WAITINGFORACTIVATION并且基本上锁定了应用程序。为了实现这个目的,我将为这种情况创建一些同步方法,但是什么才能使它正常工作并一直异步?

编辑:要求LogOn()和GetOptions()

的附加代码片段
class RestConnection
{
      private string user;
      private string password

     private XDocument convertToXDoc(string functionName, IDictionary<string,string> parameters) {} //not shown, but this just creates an XML document in the required format for the REST service to consume.
     private async Task<XDocument> SendCommand(XDocument commandDocument)
     {
          XDocument responseData = null;
          byte[] data = Encoding.UTF8.GetBytes(commandDoc.ToString());

        HttpWebRequest request = WebRequest.CreateHttp(this.serverUrl);
        request.Method = "POST";
        request.ContentType = "text/xml";
        request.ContentLength = data.Length;
        using (var requestStream = await request.GetRequestStreamAsync())
        {
            await requestStream.WriteAsync(data, 0, data.Length);
        }

            HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse;

            using (var responseStream = response.GetResponseStream())
            {
                responseData = XDocument.Load(responseStream);
            }

           return responseData;
    }

   public async Task LogIn()
    {
        var parameters = new Dictionary<string, string>();
        parameters.Add("USERNAME", userName);
        parameters.Add("PASSWORD", passWord);
        parameters.Add("CORELICTYPE", String.Empty);
        parameters.Add("REMOTEAUTH", "False");

        var xmlCommand = ConvertMethodToXml("LoginUserEx3", parameters);

        var response = await SendCommand(xmlCommand);

        //read response
            switch (response.Root.Element("RESULTS").Element("RESULTVAL").Value)
            {
                case "0":
                    sessionId = response.Root.Element("SESSIONID").Value;
                    pingRequired = response.Root.Element("PINGTIME").Value != "0";
                    if (pingRequired)
                    {
                        pingInterval = int.Parse(response.Root.Element("PINGTIME").Value);
                        pingTimer = new Timer(pingInterval);
                        pingTimer.Elapsed += PingServerRequired;
                        pingTimer.Start();
                    }
                    loggedIn = true;
                    break;
                //removed other cases for example since they all throw exceptions
                default:
                    loggedIn = false;
                    throw new ConnectionException("Error");
            }
    }
 }

GetOptions()采用与LogIn()方法相同的格式,但它从解析返回的XDocument中返回Task<List<Options>>

1 个答案:

答案 0 :(得分:1)

问题在于:

{
  Connection = null;
  DialogResult = DialogResult.Abort; //<<------ this
  MessageBox.Show("Invalid Credentials, Try Again.");
}

分配DialogResult自动关闭您的表单并传入您传入的结果。删除该行,您就可以了(特别是如果您希望对话框永不关闭)。