使用Windows窗体中的Web浏览器控件进行非UI阻止

时间:2018-02-19 13:15:35

标签: c# .net async-await task webbrowser-control

我有一个带有webbrowser控件的小型Windows窗体应用程序(此处为:wb),它执行一些任务,例如登录门户,搜索项目,更改某些属性/复选框以及提交表单。 通过更改标签的文本来宣布完成的每个步骤,以便用户可以看到当前所做的事情。

处理与webbrowser控件交互的最佳方法是什么,以便UI不会被阻止?

我试过这种方式:

private async void buttonStart_Click(object sender, EventArgs e)
{
    //Here it is still possible to access the UI thread
    buttonStart.Enabled = false;

    //do the background work
    await Task.Run(() =>
    {
        Login();
        CallProject();
        TriggerTask1();
        TriggerTask2();
    });

    //back on UI thread
    buttonStart.Enabled = false;
}

private void Login()
{
    //may not access labelProgress from this thread
    //labelProgress.Text = "logging in";

    Invoke(new Action(() =>
    {
        //can access labelProgress here
        labelProgress.Text = "logging in";

        //can access wb here
        wb.Navigate(Settings.Default.LoginUrl);
        WebBrowserTools.Wait(wb);

        wb.Document.GetElementById("username").InnerText = Settings.Default.LoginUsername;
        wb.Document.GetElementById("password").InnerText = Settings.Default.LoginPassword;
        wb.Document.Forms[0].InvokeMember("submit");
        WebBrowserTools.Wait(wb);

        labelProgress.Text = "logged in successfully";
    }));
}

每次访问webbrowser或任何其他ui控件(如labelProgress)都必须包含在Invoke(new Action(() => {}));中。 有没有更好的方法(使用现代版本的.NET Framework> = 4.5)?

1 个答案:

答案 0 :(得分:2)

您正在以非预期的方式使用async-await。

This interview with Eric Lippert helped me to understand async-await。在中间某处搜索async-await。

他将async-await与一位不得不做早餐的厨师进行了比较。在他用水壶煮水煮茶后,他决定不做任何事情,直到水煮沸,然后他煮茶,开始烘烤面包。在面包烤好之前不要做任何事情。

如果不做任何事情,只要厨师等待另一个进程结束他会开始做其他事情,等待第一件事情完成之前,会快得多:

Start boiling water
Start toasting bread
wait until the water boils
use the boiling water to start making tea
...

你会看到async-await通常是在一个线程命令某个进程执行需要花费一些时间线程无法完成的事情的情况下:从数据库中获取数据,将数据写入文件,从互联网上获取数据:线程除了等待之外什么都不做的动作。

您正确地开始了您的程序。你的厨师做了一些准备,但后来他决定雇用另一个厨师为他做工作,而你的厨师什么都不做,只能等到另一个厨师完成工作。为什么你的厨师不这样做?

更典型的async-await如下:

async void buttonStart_Click(object sender, EventArgs e)
{
    buttonStart.Enabled = false;

    await LoginAsync();
    await CallProjectAsync();
    await TriggerTask1Async();
    await TriggerTask2Async();

    buttonStart.Enabled = false;
}

通常,如果您使用支持async-await的对象,这将有效。对于每个异步函数,您都知道某处有一个等待异步函数的调用。实际上,如果在不等待任何调用的情况下将函数声明为异步,则编译器会抱怨。

唉,有时候,当你的厨师做其他事情时,你的厨师不能依赖一个过程来继续。原因是因为WebBrowser类不像WebClient类那样支持async-await。

这类似于你的厨师必须做一些繁重处理的时候,而你仍然希望保持你的程序异步 - 等待切西红柿:水壶将自动煮沸水,你需要另一个厨师切片西红柿。

在async-await场景中,您通常会使用Task.Run。在您的情况下,这是在您的登录功能

async void LoginAsync()
{
    // still main thread. Notify the operator:
    labelProgress.Text = "logging in";

    // we start something that takes some time that this cook can't spend:
    // let another cook do the logging in
    await Task.Run( () =>
    {
        wb.Navigate(Settings.Default.LoginUrl);
        WebBrowserTools.Wait(wb);
        wb.Document.GetElementById("username").InnerText = Settings.Default.LoginUsername;
        wb.Document.GetElementById("password").InnerText = Settings.Default.LoginPassword;
        wb.Document.Forms[0].InvokeMember("submit");
        WebBrowserTools.Wait(wb);
    }

    // back in main thread:
    labelProgress.Text = "logged in successfully";
}

所以诀窍是:尽可能经常使用异步 - 等待准备好的类。只有当你的厨师必须做一些冗长的事情但他想要自由地做其他事情时,请执行Task.Run()。等待你的厨师需要确定其他厨师完成了它的工作。当你有其他事情要做时,不要等待。让你的厨师成为处理用户界面的人。