执行C#函数时异步等待
问题描述:
我有一个阻塞函数,它执行异步MySQL查询并在获得结果时返回结果。原因是异步的是这个程序在查询过程中不允许锁定。执行C#函数时异步等待
该函数在用户按下按钮时调用,因此可能会在第一次查询完成之前多次调用该函数。我想我可以添加一个布尔值来检查查询是否正在执行,并让函数在继续之前等待,直到它完成,但它没有按预期工作。我使用的两个DoEvents()存在一些问题。如果你注释掉任何一个,它就会运行得很好,除了UI冻结。
如何让函数在执行查询时执行非阻塞等待,以及在查询本身被取回时执行非阻塞等待?我真的更愿意将它保留在一个线程上,因为函数本身阻塞了调用它的代码。任何帮助将不胜感激!
public Exception LastError;
public MySqlConnection Conn;
public MySqlDataReader Reader;
public bool IsExecuting = false;
public MySqlDataReader MySQL_Query(string Query, [Optional] params string[] Values)
{
while (IsExecuting)
{
System.Windows.Forms.Application.DoEvents();
System.Threading.Thread.Sleep(20);
}
if (IsConnected() == false)
ConnectToDatabase();
for (int i = 0; i < Values.Length; i++)
Values[i] = MySQL_SafeValue(Values[i]);
if (Reader != null && Reader.IsClosed == false)
Reader.Close();
IsExecuting = true;
try
{
MySqlCommand Cmd = new MySqlCommand(String.Format(Query, Values), Conn);
IAsyncResult aRes = Cmd.BeginExecuteReader();
while (!aRes.IsCompleted)
{
System.Windows.Forms.Application.DoEvents();
System.Threading.Thread.Sleep(20);
}
Reader = Cmd.EndExecuteReader(aRes);
IsExecuting = false;
}
catch (Exception e)
{
IsExecuting = false;
LastError = e;
return null;
}
return Reader;
}
答
有很多方法可以做异步工作,从直接使用线程池到像BackgroundWorker这样的助手。
但是,这并不能回答你的主要问题,这是一个有点矛盾的问题,即你想做一个无阻塞的等待。如果你已经在执行,我会建议你不要阻止,然后忽略这个请求并且什么也不做。在这种情况下,您可能需要提供一些反馈意见以说明“已经在工作”。
现在到您的代码的实际问题。正如亚当指出,你真的不应该使用DoEvents和睡眠。而是将长时间运行的工作项发布到某个后台任务,并使用一个标志在UI线程和运行任务的线程之间进行同步,例如,
/// <summary>
/// Used to prevent more than one worker.
/// </summary>
private bool working = false;
/// <summary>
/// Must use a lock to synch between UI thread and worker thread.
/// </summary>
private object stateLock = new object();
/// <summary>
/// Used to pass custom args into the worker function.
/// </summary>
private class Data
{
public string query;
public string[] values;
}
/// <summary>
/// Called in your UI thread in response to button press.
/// </summary>
/// <param name="Query"></param>
/// <param name="Values"></param>
public void UiRequestToDoWork(string Query, params string[] Values)
{
lock (stateLock)
{
if (working)
{
// Do nothing!
Trace.WriteLine("Already working!");
}
else
{
var backgroundWorker = new System.ComponentModel.BackgroundWorker();
backgroundWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerAsync(new Data { query = Query, values = Values });
this.working = true;
}
}
}
/// <summary>
/// Does all the background work.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
try
{
Data data = e.Argument as Data;
if (data != null)
{
// Do your query in here - just simulating work with a sleep.
Trace.WriteLine("Working...");
System.Threading.Thread.Sleep(500);
// Note: you can't access the UI directly here in the worker thread. Use
// Form.Invoke() instead to update the UI after your work is done.
}
}
finally
{
// Note the use of finally to be safe if exceptions get thrown.
lock (stateLock)
{
this.working = false;
}
Trace.WriteLine("Finished!");
}
}
答
虽然这不是一个选项,当你问你的问题,如果你可以升级到.NET 4.5现在是向异步操作更清洁的方式,而在你会为同样的方式本质上还是写同步代码。这涉及使用新的async
和await
关键字。
参见:
An Async Primer为介绍新功能
而here是一个SO问题特别引用MySQL连接。