我在使异步操作正常工作(异步新手)时遇到了一些麻烦。我的目标是让按钮“加载数据”熄灭并从数据库中检索一些数据并填充网格。对于某些用户而言,数据库可能会有点遥远,此操作可能需要一些时间。考虑到这一点,我希望用户能够选择取消并选择检索较小的数据集。
我主要使用当前流程:
这一切都很好,除了,如果用户单击“取消”,仍然需要花费与为网格清空所有数据所花费的时间相同的时间。这使我相信,长时间运行的操作实际上并没有被取消...但是,当我在“ FindForLocationAsync”方法中进行调试时,如果用户请求执行以下操作,则取消令牌确实会停止迭代操作并从该方法中早返回取消。
我已经花了很长时间阅读了很多东西,但是现在我陷入了僵局。任何帮助将不胜感激。
取消令牌来源
CancellationTokenSource cancellationTokenSource = null;
按钮单击方法
private async void btnSearch_Click(object sender, EventArgs e)
{
gridLog.DataSource = null;
Cursor = Cursors.WaitCursor;
if (btnSearch.Text.ToLower().Contains("load"))
{
btnSearch.Text = "Cancel";
btnSearch.ForeColor = Color.White;
btnSearch.BackColor = Color.Red;
//get params to pass
/* snip */
cancellationTokenSource = new CancellationTokenSource();
await Task.Run(() =>
{
var ds = DocLog.FindForLocationAsync(docType, subType, days, currLocation.ID, cancellationTokenSource.Token).Result;
gridLog.DataSource = ds;
});
btnSearch.Text = "Load Data...";
btnSearch.ForeColor = Color.Black;
btnSearch.BackColor = Color.FromArgb(225, 225, 225);
}
else
{
cancelSearch();
btnSearch.Text = "Load Data...";
btnSearch.ForeColor = Color.Black;
btnSearch.BackColor = Color.FromArgb(225, 225, 225);
}
Cursor = Cursors.Default;
}
取消方法
private void cancelSearch()
{
if (cancellationTokenSource != null) cancellationTokenSource.Cancel();
}
长时间运行方法
public async static Task<BindingList<DocLog>> FindForLocationAsync(string DocType, string SubType, int? LastXDays, Guid LocationID, CancellationToken CancellationToken)
{
BindingList<DocLog> dll = new BindingList<DocLog>();
using (SqlConnection sqlConnection = new SqlConnection(Helper.GetConnectionString()))
{
sqlConnection.Open();
using (SqlCommand sqlCommand = new SqlCommand((LastXDays == null) ? "DocLogGetAllForLocation" : "DocLogGetAllForLocationLastXDays", sqlConnection))
{
sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
sqlCommand.Parameters.Add("@DocType", SqlDbType.NVarChar, 30).Value = DocType.Trim();
sqlCommand.Parameters.Add("@SubType", SqlDbType.NVarChar, 30).Value = SubType.Trim();
sqlCommand.Parameters.Add("@LocationID", SqlDbType.UniqueIdentifier).Value = LocationID;
if (LastXDays != null) { sqlCommand.Parameters.Add("@NumberOfDays", SqlDbType.Int).Value = LastXDays; }
SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();
await Task.Run(() =>
{
while (sqlDataReader.Read())
{
if (CancellationToken.IsCancellationRequested)
{
dll = new BindingList<DocLog>();
break;
}
else
{
DocLog dl = readData(sqlDataReader);
dll.Add(dl);
}
}
});
}
}
return dll;
}
答案 0 :(得分:1)
这是将您的代码修改为C#惯用的async
:
请注意以下几点:
Task
(实际上,Task
用于多线程(Task.Run
)和async-IO。
async
-IO API(例如SqlDataReader
,FileStream
,NetworkStream
等),那么您可能不需要不想使用Task.Run
。.ConfigureAwait(false)
之外,以允许在可用的后台线程中调用完成回调,这意味着不会强制执行UI线程运行后台代码。
.ConfigureAwait(false)
and are working on a solution垃圾邮件才符合人体工程学。Task<T>.Result
或Task.Wait()
,因为它们会阻塞线程并带来死锁的风险(因为无法在阻塞的线程上运行继续回调)。仅在验证任务已完成后才使用Task<T>.Result
(或只需执行await task
)。CancellationToken
传递给您调用的每个子方法Async
。其他nitpicks:
using()
语句,并在创建SqlConnection.OpenAsync
之后调用SqlCommand
。camelCase
而不是PascalCase
。this.
,以便在视觉上与本地标识符区分开。if( this.x != null ) this.x.Foo()
并不完全安全,因为在多线程程序中x
可以用{em> if
和{{ 1}}呼叫。而是使用.Foo()
运算符,该运算符保留本地引用,以防止地毯从您的下方拉出(它的工作方式为:?.
,保证了线程安全)。X lx = this.x; if( lx != null ) lx.Foo()
是(可能是)UI组件,不应从概念上“后台”的函数(如BindingList
方法中返回),因此我先返回FindForLocationAsync
然后返回UI将List<T>
包裹在List<T>
中。代码:
BindingList<T>