在哪里停止使用async / await关键字?

4
我有一个简单的针对DB2/400的心跳方法:

我有一个简单的针对DB2/400的心跳方法:

public bool CheckConnection()
        {
            try
            {
                using (OleDbConnection db = new OleDbConnection( this.conString ))
                {
                    OleDbCommand cmd = new OleDbCommand();
                    cmd.CommandText = "select 1 from sysibm.sysdummy1";
                    cmd.Connection = db;
                    db.Open();
                    cmd.ExecuteReader();
                    db.Close();
                    return true;
                }
            }
            catch (Exception)
            {
                return false;
            }
        }

当我的应用程序运行时,我希望使用它,当然我不想阻塞表单其余部分的执行。

我的表单中的主要方法:

public FrmMain()
    {
        InitializeComponent();
        PrepareFormTexts();
        PrepareFormData();
        PrepareStateClass();
        CheckDbConnection();
        //Initialize Data Access for AS400
        Dal = JDE8Dal.Instance;
        Dal.conString = Properties.Settings.Default.conAS400;
        //Initialize Icons
        picCon.Image = Properties.Resources.ledGreen;
        picStatus.Image = Properties.Resources.ledGreen;
        // Load recording from file if they exist
        PrepareRecordings(AppState.DataFileName,'|');
    }

CheckDbConnection方法:

    private async Task<bool> CheckDbConnection()
    {
        return await Task.Factory .StartNew(() => Dal.CheckConnection());
    }

我认为程序可以正常运行,但是出现了一个警告。

警告1:由于此调用未被等待,当前方法的执行在调用完成之前继续进行。请考虑将“await”运算符应用于调用结果。

我应该忽略它吗?我应该在主方法中加入async吗?

更新:在下面的讨论中,我发现由于我正在使用.NET 4.0的异步包,我无法使我的dal方法非阻塞。我唯一能做的就是使用async/await + task.factory.startnew来保持我的应用程序在后台工作时dal在后台工作。

以下是代码:

public FrmMain()
    {
        InitializeComponent();
        Load += FormLoadInit;
    }

    private async void FormLoadInit(object sender, EventArgs e)
    {
        PrepareFormTexts();
        PrepareFormData();
        PrepareStateClass();
        txtLot.Select();
        //Initialize Data Access for AS400
        Dal = JDE8Dal.Instance;
        Dal.conString = Properties.Settings.Default.conAS400;
        // Load recording from file if they exist
        PrepareRecordings(AppState.DataFileName, '|');
        bool cn = await Task.Factory.StartNew(() => Dal.CheckConnection());
        //Initialize Icons
        picCon.Image = cn ? Resources.ledGreen : Resources.ledRed;
        picStatus.Image = Properties.Resources.ledGreen;

    }

4
CheckDbConnection 的结果是否真的会在任何时候被使用? - Marc Gravell
1
你有没有查看过这个链接 - Alex Filipovici
@MarcGravell 抱歉:主方法还没有准备好。当然,我会在那里使用它来设置各种启动参数。只是为了看看它是否编译通过。 - e4rthdog
4个回答

4
警告告诉您,您正在启动并忘记此异步任务。 您实际上从未使用操作的结果,也没有存储Task本身以允许任何将来的代码依赖于它的结果(甚至不知道它何时完成)。
这不会破坏任何东西,但也不是真正有用的。 您可能干脆不调用该方法。 现在,在您的情况下,这只是一个虚拟列表,因此您需要问自己要测试什么。 您是否想显示一些虚拟值,仅作为概念证明? 如果是,则需要对代码进行一些更改才能实现目标。
要实际使用查询结果,您需要await它,而您无法在构造函数中执行此操作。 您将希望将代码移动到Form Load事件处理程序中,并将处理程序标记为async,以便可以await其中。
您还面临的另一个问题是,要创建异步方法,您正在启动将在线程池中运行的新任务,然后让它执行阻塞方法。 使用异步/等待模型的主要优点是您不需要这样做。 您应该使用直接为返回值提供Task<T>的数据库查询方法,而不会阻塞。 然后,您可以await这些任务(或只是返回它们),以使您的应用程序在等待时没有被阻止的线程。
您的CheckConnection方法应该如下所示:
public async Task<bool> CheckConnection()
{
    try
    {
        using (OleDbConnection db = new OleDbConnection(this.conString))
        {
            OleDbCommand cmd = new OleDbCommand();
            cmd.CommandText = "select 1 from sysibm.sysdummy1";
            cmd.Connection = db;
            db.Open();
            await cmd.ExecuteReaderAsync();
            return true;
        }
    }
    catch (Exception)
    {
        return false;
    }
}

CheckDbConnection 的存在是不必要的。没有必要在 Task 中包装 CheckConnection,因为它已经返回了一个任务。


@e4rthdog DAL 应该公开一些返回 TaskTask<T> 的方法。然后 UI 可以 await 该任务,以执行非阻塞等待,直到该操作的结果完成。任何包含 await 的方法都需要在其签名中添加 asyncasync 除了告诉编译器“嘿,我在这里有一个 await”之外,什么也不做。这是一种确保向后兼容性的方式,以防某人将变量/方法/任何内容命名为 await - Servy
如果我想正常使用dal方法,即使将其作为Task<bool>返回,我是否可以这样做?此外,在特定示例中,我只想让数据库检查方法在后台运行,并在完成时根据结果将图片变红或绿。同时,我希望表单是可操作的... - e4rthdog
@e4rthdog 是的,即使该方法返回一个任务而不是直接返回布尔值,你仍然可以使用它。对于DAL方法来说,“链接”它们的async/await操作是完全可接受的。如果一个复杂的操作使用了一个更简单的DAL方法,它可以将自己标记为“async”,并“await”这个更简单的方法,这样它也会返回一个“Task”,代码就可以像顺序执行一样编写,而不是异步执行(尽管实际上是异步执行,这就是很酷的地方)。 - Servy
请问您能否在您的回答中添加正确的dal方法,使其应该是Task<bool>?(我的第一个代码片段) - e4rthdog
需要XP支持,但也需要一种简单的方法来使我的应用程序在DAL中发生某些事情时不会阻塞,并且我希望使用async/await的简单性,而无需转到任务和continuewith...我想这是我能做的最好的选择吧?只使用async/await来避免阻塞,并让dal阻塞后台线程。 - e4rthdog
显示剩余8条评论

0
构造函数不能是异步的(至少每次尝试时我总是遇到编译器错误)。但事件处理程序可以是异步的。我认为大部分初始化代码都应该放在Form.Load处理程序中。
public FrmMain()
{
    InitializeComponent();
    Load+=OnLoaded;
}

private async void Onloaded(object sender, EventArgs args)
{
    PrepareFormTexts();
    PrepareFormData();
    PrepareStateClass();
    await CheckDbConnection();
    //Initialize Data Access for AS400
    Dal = JDE8Dal.Instance;
    Dal.conString = Properties.Settings.Default.conAS400;
    //Initialize Icons
    picCon.Image = Properties.Resources.ledGreen;
    picStatus.Image = Properties.Resources.ledGreen;
    // Load recording from file if they exist
    PrepareRecordings(AppState.DataFileName,'|');
}

理论上,当到达 await 行时,控制权将移动到调用方法,并在完成后恢复到该行之后...如果这是真的,我真正想做的是检查数据库连接并同时进行其他操作... - e4rthdog

0
public FrmMain()
{
    InitializeComponent();
    PrepareFormTexts();
    PrepareFormData();
    PrepareStateClass();
    Task delayTask = CheckDbConnection();
    //Initialize Data Access for AS400
    Dal = JDE8Dal.Instance;
    Dal.conString = Properties.Settings.Default.conAS400;
    //Initialize Icons
    picCon.Image = Properties.Resources.ledGreen;
    picStatus.Image = Properties.Resources.ledGreen;
    // Load recording from file if they exist
    PrepareRecordings(AppState.DataFileName,'|');
}

1
这将消除警告,但仍然没有解决警告的含义,甚至没有处理任务。这实际上与添加警告抑制几乎相同。 - Servy

0
为了实现您的范围,也许您应该考虑使用响应式扩展框架。下面是一个基于您问题示例的样本实现。您需要向您的表单添加两个标签(label1label2)。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

//get Reactive Extensions and reference them in your project.
//Reactive Extensions for .NET (Rx .NET) http://msdn.microsoft.com/en-us/data/gg577610
using System.Reactive.Linq;

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Load += Form1_Load;
        }

        async void Form1_Load(object sender, EventArgs e)
        {
            //do independent stuff
            PrepareSomeStuff();

            //notify the DB checking
            label1.Text = "Checking DB";

            //declare an IObservable
            IObservable<bool> startAsync = Observable.Start<bool>(() => CheckConnection()).FirstAsync<bool>();

            //do some other independent stuff
            PrepareSomeOtherStuff();

            //wait for the IObservable to return data
            bool isDbReady = await startAsync;
            label1.Text = "Ready";
        }

        private void PrepareSomeOtherStuff()
        {
            //do some other stuff
            label2.Text += "\r\nDo some other stuff";
        }

        private void PrepareSomeStuff()
        {
            //do stuff
            label2.Text = "Do stuff";
        }

        private bool CheckConnection()
        {
            //do stufff
            System.Threading.Thread.Sleep(5000);
            return true;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接