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

C# 需要同步时导致死锁的异步事件,c#,user-interface,asynchronous,async-await,deadlock,C#,User Interface,Asynchronous,Async Await,Deadlock,我创建了一个类来使用RESTAPI。我编写这个类是为了与web服务异步通信,因为我最初并不认为需要同步运行任何东西。现在,我遇到了一种情况,我意识到在我的应用程序中,使用异步方法并不适合某个特定情况,因为它运行无序,并导致异常,因为应用程序试图调用一个尚未准备好的方法。我不能100%确定为什么会发生这种情况,但我认为这是因为在我的UI中的async void事件中调用了这些方法。以下是一些代码片段,显示了这种情况的示例: class MyForm : Form { private Re

我创建了一个类来使用RESTAPI。我编写这个类是为了与web服务异步通信,因为我最初并不认为需要同步运行任何东西。现在,我遇到了一种情况,我意识到在我的应用程序中,使用异步方法并不适合某个特定情况,因为它运行无序,并导致异常,因为应用程序试图调用一个尚未准备好的方法。我不能100%确定为什么会发生这种情况,但我认为这是因为在我的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事件尚未完成,连接为null,因此调用了NullReferenceException。此外,如果我继续通过并忽略异常,则会抛出ObjectDisposedException,因为我们现在在使用块之外

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

Task t = conn.LogOn();
while(!t.IsCompleted)
    Thread.Sleep(50);
这并没有死锁,但它确实会永远旋转。每次我在While条件下检查断点时,任务的状态总是等待激活,并且基本上锁定了应用程序。为了让它正常工作,我将为这种情况创建一些同步方法,但是什么能让它正常工作并且始终是异步的呢

编辑:为LogOn()和GetOptions()请求的其他代码段

类RestConnection
{
私有字符串用户;
私有字符串密码
private XDocument convertToXDoc(string functionName,IDictionary parameters){}//未显示,但这只是以REST服务使用的所需格式创建了一个XML文档。
专用异步任务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;
使用(var requestStream=await request.GetRequestStreamAsync())
{
wait requestStream.WriteAsync(数据,0,数据.Length);
}
HttpWebResponse=wait request.GetResponseAsync()作为HttpWebResponse;
使用(var responseStream=response.GetResponseStream())
{
responseData=XDocument.Load(responseStream);
}
返回响应数据;
}
公共异步任务登录()
{
var参数=新字典();
参数。添加(“用户名”,用户名);
参数。添加(“密码”,密码);
parameters.Add(“CORELICTYPE”,String.Empty);
参数。添加(“REMOTEAUTH”、“False”);
var xmlCommand=ConvertMethodToXml(“LoginUserEx3”,参数);
var响应=等待发送命令(xmlCommand);
//读取响应
开关(response.Root.Element(“RESULTS”).Element(“RESULTVAL”).Value)
{
案例“0”:
sessionId=response.Root.Element(“sessionId”).Value;
pingRequired=response.Root.Element(“PINGTIME”).Value!=“0”;
如果(需要)
{
pingInterval=int.Parse(response.Root.Element(“PINGTIME”).Value);
pingTimer=新计时器(pingInterval);
pingTimer.appeased+=PingServerRequired;
pingTimer.Start();
}
loggedIn=true;
打破
//例如,删除了其他案例,因为它们都抛出异常
违约:
loggedIn=false;
抛出新的ConnectionException(“错误”);
}
}
}
GetOptions()与LogIn()方法的格式相同,只是它通过解析返回的XDocument返回一个
任务。

问题在于:

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

DialogResult=DialogResult.Abort;//您可能应该在
Resconnection.LogIn
Resconnection.GetChoices
@DanielKelley当然,给我一分钟时间。@JNYRanger:检查我下面的更新。如果不想关闭表单,您不应该使用
DialogResult=DialogResult.Abort;
。问题是如果登录失败,我不希望对话框关闭。我希望用户看到登录失败的原因,并允许他们再试一次。@JNYRanger:好的,这样他们在没有实际登录的情况下就无法退出LogOnDialog。没问题,代码的工作原理是一样的,尽管您可以删除
if(connection!=null)
。LogOnDialog的
Closed
事件是在
saveButton\u Click
完成后调用的,异步等等。啊……我从来都不知道分配DialogResult会关闭模态表单!我后来实际上添加了它,以防止它继续,因为它以某种方式在MyForm b的result==DialogResult.OK块中结束在按钮的单击事件完成并设置之前!但是,我确实将该按钮指定为表单的AcceptButton,因此这可能是问题的一部分?@JNYRanger:不,更改
DialogResult
将以该结果关闭表单。感谢您的建议!我将进行一些测试,然后希望接受您的建议事后回答!
{
  Connection = null;
  DialogResult = DialogResult.Abort; //<<------ this
  MessageBox.Show("Invalid Credentials, Try Again.");
}