C# 异步操作已取消,但仍需要时间更新网格
我在让异步操作正常工作时遇到了一点问题(对于异步来说是新的)。我的目标是有一个“加载数据”按钮,从数据库中检索一些数据并填充网格。对于某些用户来说,数据库可能比较远,此操作可能需要一些时间。考虑到这一点,我希望用户能够选择取消并选择检索较小的数据集 我让它主要与当前流程一起工作:C# 异步操作已取消,但仍需要时间更新网格,c#,async-await,C#,Async Await,我在让异步操作正常工作时遇到了一点问题(对于异步来说是新的)。我的目标是有一个“加载数据”按钮,从数据库中检索一些数据并填充网格。对于某些用户来说,数据库可能比较远,此操作可能需要一些时间。考虑到这一点,我希望用户能够选择取消并选择检索较小的数据集 我让它主要与当前流程一起工作: 用户单击“加载数据…”按钮 按钮更改为“取消”并 检索数据的异步操作启动 数据被检索并保存 网格已填充 这一切都很好,但如果用户单击“取消”,则仍需要与获取网格的所有数据所需的时间相同的时间来清空网格。这让我相信长期运
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;
}
公共异步静态任务FindForLocationAsync(字符串DocType、字符串子类型、int?LastXDays、Guid LocationID、CancellationToken CancellationToken)
{
BindingList dll=新建BindingList();
使用(SqlConnection SqlConnection=newsqlconnection(Helper.GetConnectionString()))
{
sqlConnection.Open();
使用(SqlCommand SqlCommand=newsqlcommand((LastXDays==null)?“DocLogGetAllForLocation”:“DocLogGetAllForLocationLastXDays”,sqlConnection))
{
sqlCommand.CommandType=System.Data.CommandType.StoredProcess;
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();
等待任务。运行(()=>
{
while(sqlDataReader.Read())
{
if(CancellationToken.IsCancellationRequested)
{
dll=新绑定列表();
打破
}
其他的
{
DocLog dl=readData(sqlDataReader);
添加(dl);
}
}
});
}
}
返回dll;
}
这是您修改为C语言的代码#惯用异步
:
注意以下几点:
- 异步代码通常指涉及异步IO的操作,其中完成信号(以及随后的完成回调)基本上是由硬件中断和操作系统产生的-不应将其与并发性(即多线程)混淆即使在另一个线程上运行的代码也可以在概念上建模为
(实际上,任务
用于多线程(任务
)和异步IO)。Task.Run
- 无论如何,要点是:如果您使用的是
-IO API(例如async
,SqlDataReader
,FileStream
,等等),那么您可能不想使用NetworkStream
任务。运行
- 无论如何,要点是:如果您使用的是
- 必须在UI线程中运行的代码之外(即WinForms和WPF UI代码),以允许在可用的后台线程中调用完成回调,这意味着UI线程不会被迫运行后台代码。
- C语言设计师意识到垃圾
的可怕的人体工程学.ConfigureAwait(false)
- C语言设计师意识到垃圾
- 一般来说,永远不要使用
或Task.Result
,因为它们会阻塞线程并引入死锁风险(因为无法在阻塞的线程上运行连续回调)。仅在验证任务已完成后使用Task.Wait()
(或只需执行Task.Result
)wait Task
- 您应该将
传递给您调用的每个子CancellationToken
方法Async
- 创建
后,您可以在同一缩进级别上使用()语句组合SqlCommand
,并调用
SqlConnection.OpenAsync
- 参数应该是
而不是camelCase
PascalCase
- 对实例成员(字段、方法、属性等)的引用应以
作为前缀,以便从视觉上区分它们与本地标识符this.
- 执行
不是完全安全的,因为在多线程程序中,if(this.x!=null)this.x.Foo()
可以被x
和if
调用之间的另一个值替换。相反,请使用.Foo()
操作符,该操作符保留一个本地引用,以防止地毯从下方被拉出(其工作原理如下:?。
,保证线程安全)X lx=this.X;if(lx!=null)lx.Foo()
是(可以说)一个UI组件,不应该从概念上的“后台”函数(如BindingList
方法)返回,因此我返回一个FindForLocationSync
,然后UI将List
包装在List
中BindingList
private async void btnSearch\u单击(对象
private async void btnSearch_Click(object sender, EventArgs e)
{
this.gridLog.DataSource = null;
this.Cursor = Cursors.WaitCursor;
if (this.btnSearch.Text.ToLower().Contains("load"))
{
this.btnSearch.Text = "Cancel";
this.btnSearch.ForeColor = Color.White;
this.btnSearch.BackColor = Color.Red;
//get params to pass
/* snip */
this.cancellationTokenSource = new CancellationTokenSource();
List<DocLog> list = await DocLog.FindForLocationAsync(docType, subType, days, currLocation.ID, cancellationTokenSource.Token);
gridLog.DataSource = new BindingList<DocLog>( list );
this.btnSearch.Text = "Load Data...";
this.btnSearch.ForeColor = Color.Black;
this.btnSearch.BackColor = Color.FromArgb(225, 225, 225);
}
else
{
CancelSearch();
this.btnSearch.Text = "Load Data...";
this.btnSearch.ForeColor = Color.Black;
this.btnSearch.BackColor = Color.FromArgb(225, 225, 225);
}
this.Cursor = Cursors.Default;
}
private void CancelSearch()
{
this.cancellationTokenSource?.Cancel();
}
public async static Task<List<DocLog>> FindForLocationAsync(string DocType, string SubType, int? LastXDays, Guid LocationID, CancellationToken cancellationToken)
{
List<DocLog> dll = new List<DocLog>();
using (SqlConnection sqlConnection = new SqlConnection(Helper.GetConnectionString()))
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
await sqlConnection.OpenAsync(cancellationToken).ConfigureAwait(false);
sqlCommand.CommandText = (LastXDays == null) ? "DocLogGetAllForLocation" : "DocLogGetAllForLocationLastXDays";
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; }
using( SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false) )
{
while (await sqlDataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
if (cancellationToken.IsCancellationRequested) break;
DocLog dl = readData(sqlDataReader);
dll.Add(dl);
}
}
}
return dll;
}