为了账号安全,请及时绑定邮箱和手机立即绑定

异步操作已取消但仍需要时间来更新网格

异步操作已取消但仍需要时间来更新网格

C#
牧羊人nacy 2022-07-23 09:12:29
我在使异步操作正常工作时遇到了一些麻烦(异步操作的新手)。我的目标是让“加载数据”按钮退出并从数据库中检索一些数据并填充网格。对于某些用户而言,数据库可能有些远,此操作可能需要一些时间。考虑到这一点,我希望用户能够选择取消并选择检索较小的数据集。我主要使用当前流程:用户单击“加载数据...”按钮按钮更改为“取消”,异步操作开始检索数据检索数据并填充网格这一切都运行良好,除了,如果用户单击取消,它仍然需要相同的时间来获取所有数据以使网格变为空。这让我相信长时间运行的操作实际上并没有被取消......但是,当我在“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();}长时间运行方法
查看完整描述

1 回答

?
RISEBY

TA贡献1856条经验 获得超5个赞

这是您修改为 C# 惯用的代码async

请注意以下事项:

  • 异步代码通常是指涉及异步 IO 的操作,其中完成信号(和后续完成回调)基本上由硬件中断和操作系统产生 - 它不应与并发(即多线程)混淆,即使在另一个线程上运行的代码也可以在概念上被建模为 a Tasktoo(实际上,Task用于多线程 ( Task.Run) 和 async-IO)。

    • 无论如何,重点是:如果您使用的是async-IO API(例如SqlDataReaderFileStreamNetworkStream等),那么您可能不想使用Task.Run.

  • 除了必须在 UI 线程中运行的代码(即 WinForms 和 WPF UI 代码)之外,您应该始终使用它.ConfigureAwait(false)来允许在可用的后台线程中调用完成回调,这意味着 UI 线程不会被迫运行后台代码.

  • 一般来说,永远不要使用Task<T>.Resultor Task.Wait(),因为它们会阻塞线程并引入死锁的风险(因为不能在阻塞的线程上运行延续回调)。仅Task<T>.Result在您验证任务已完成(或仅执行await task)后使用。

  • 您应该将 传递给您调用的CancellationToken每个子方法。Async

其他挑剔:

  • 您可以using()在同一缩进级别组合语句并SqlConnection.OpenAsync在创建SqlCommand.

  • camelCase参数不应该PascalCase

  • 对实例成员(字段、方法、属性等)的引用应加上前缀,this.以便在视觉上与本地标识符区分开来。

  • 这样做if( this.x != null ) this.x.Foo()并不完全安全,因为在多线程程序x中,可以在调用和调用之间用另一个值替换。而是使用保留本地参考的操作员来防止地毯从您下方拉出(它的工作原理如下:保证是线程安全的)。if.Foo()?.X lx = this.x; if( lx != null ) lx.Foo()

  • BindingList是(可以说)一个 UI 组件,不应该像你的FindForLocationAsync方法那样从概念上的“背景”函数返回,所以我返回 a List<T>,然后 UI 将List<T>a 包装在BindingList<T>.

代码:

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;

}


查看完整回答
反对 回复 2022-07-23
  • 1 回答
  • 0 关注
  • 113 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信