【问题标题】:C# Windows application show picture box until background process finishC# Windows 应用程序显示图片框,直到后台进程完成
【发布时间】:2017-11-24 06:26:11
【问题描述】:

我必须调用我的 API 并显示等待 PictureBox(wait.gif) 直到 API 完成并返回数据以将我的自定义控件绑定到 tablepanellayout。

我不能在这里使用BackgroundWorker 类,因为它与我在自定义控件中的子控件存在跨线程问题。

这里我唯一的想法是从主线程调用子线程,但直到它被完全执行显示PictureBox(wait.gif) 并阻止要执行的其余代码。

任何人都可以建议我如何做到这一点,或者请提供一些代码 sn-p 例如。

【问题讨论】:

  • 所以,您的代码中存在一些奇怪的问题,导致您无法使用非常基本的后台工作程序。然后你决定不修复它,而是创建一些hacky hack,它会阻塞主应用程序线程:即引入大量潜在错误和奇怪的难以修复的错误。只修复“自定义控件中的子控件”中的错误不是更好吗?
  • 发布您的代码。没有人能猜出 your 代码有什么问题。 BGW 没有跨线程问题。它旨在通过在 UI 线程上引发进度事件来防止跨线程问题。如果您有跨线程问题,那是 您的 代码失败了。您很可能正在尝试从 DoWork 事件内部修改 UI,而不是引发进度事件并在那里更新 UI
  • 所有你需要做的就是在调用RunWorkerAsync之前改变背景,然后在UI线程中引发的RunWorkerCompleted事件中改变它。如果你想阻止人们使用UI 只是 禁用 表单,或使用模式启动屏幕。在这种情况下与线程无关
  • 无论如何,您都不需要 Bgw 或 Thread 来显示 PictureBox。您的 UI 是单线程的。我们无法为您提供其他帮助,最好的解决方案是调用该 API async 并使用 await。

标签: c# multithreading api async-await backgroundworker


【解决方案1】:

有两篇文章对我理解 async-await 有很大帮助:

对于 WinForms,您执行以下操作:

  • 如果您的数据查询函数有异步版本来获取数据,请使用该函数。想想 SqlConnection.OpenAsync、Dapper.QueryAsync 等函数
  • 如果您的数据查询器没有异步版本,请使用 Task.Run 使其异步
  • 每个调用异步函数的函数都应该声明为 async 本身
  • 每个异步函数都返回 Task 而不是 void 和 Task<TResult> 而不是 TResult
  • 唯一的例外是事件处理程序:此函数返回 void 而不是 Task
  • 在异步返回之前,应等待对其他异步函数的调用。

.

// The event handler: make async. The only one that still returns void
async void OnButton1_clicked(object sender, ...)
{
    // start fetching the data. Don't await yet, you'll have other things to do
    Task<MyData> fetchDataTask = FetchData(...);

    // meanwhile: show the user that you are busy:
    this.ShowBusy(true); // show picture box?

    // if needed do other things you can do before the data is fetched
    this.ClearTable();

    // once you have nothing meaningful to do, await for your data
    MyData fetchedData = await fetchDataTask;
    this.ProcessData(fetchedData);

    // finished: 
    this.ShowBusy(false); // remove picture box
}

获取数据的函数的异步版本:

async Task<IQueryable<MyData>> FetchDataAsync(myParams)
{
    using (SqlConnection dbConnection = new SqlConnection(...)
    {
        // open the connection, don't wait yet:
        Task taskOpen = sqlCommand.OpenAsync();

        // continue while opening:
        using (var sqlCommand = new SqlCommand(...))
        {
            cmd.Parameters.AddWithValue(...);

            // before executing the query: wait until OpenAsync finished:
            await taskOpen;

            // read the data. If nothing to do: await, otherwise use Task similar to Open
            SqlDataReader dataReader = await cmd.ExecuteReaderAsync();
            foreach (var row in dataReader)
            { 
                ... (some Await with GetFieldValueAsync
            }
        }
    }
}

我不太熟悉读取这么低级别的 SQL 数据,我更喜欢实体框架和 dapper,所以我的 SqlReader 内容可能有错误。也许有人可以纠正这一点。你仍然会明白要点。

如果你这样写,你的程序会很灵敏:当程序需要等待某事时,控制权会交还给函数的调用者,他可以继续处理,直到他遇到等待,当控制权被给予时返回给调用者,后者继续处理,等等。

请注意,如果您的程序没有等待某些东西,这对您没有帮助。如果您的主线程在几秒钟内进行了一些繁重的计算,您的程序将不会响应。考虑创建一个异步函数来为您使用 Task.Run 执行此操作

如果您这样编程,执行您的功能的所有线程都将具有相同的上下文:就好像只涉及 UI 线程。不需要互斥体、信号量、InvokeRequired 等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多