【发布时间】:2014-08-05 22:27:23
【问题描述】:
我创建了一个类来使用 REST API。我编写了这个类来与 Web 服务进行异步通信,因为我最初并不认为我需要让任何东西同步运行。现在我遇到了一种情况,我意识到使用异步方法对于我的应用程序中的一种特定情况并不理想,因为它运行不正常并导致异常,因为应用程序试图调用一个它还没有准备好的方法。我不是 100% 确定为什么会发生这种情况,但我认为这是由于在我的 UI 中的 async void 事件中调用了这些方法。下面是一些代码 sn-ps,显示了这种情况的示例:
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(),但连接仍然为空,因为 LogOnDialog 的异步事件创建了连接并在允许向调用者提供连接之前检查是否成功登录。但是,由于 Click 事件尚未完成,因此连接为 null,因此调用了 NullReferenceException。此外,如果我继续过去并忽略异常,则会引发 ObjectDisposedException,因为我们现在位于 using 块之外。
我试图通过从事件中删除 async 关键字并在 login 方法上调用 Wait() 来强制同步登录。这导致了僵局。我还尝试使用以下代码捕获任务,并为它旋转等待:
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>>。
【问题讨论】:
-
您可能应该在
Resconnection.LogIn和Resconnection.GetChoices中包含代码。 -
@DanielKelley 当然,等一下。
-
@JNYRanger:在下面查看我的更新。如果您不想关闭表单,则不应使用
DialogResult = DialogResult.Abort;。
标签: c# user-interface asynchronous async-await deadlock