RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 679560
Accepted
Fresto
Fresto
Asked:2020-06-15 17:22:46 +0000 UTC2020-06-15 17:22:46 +0000 UTC 2020-06-15 17:22:46 +0000 UTC

你能解释一下invoke的本质吗

  • 772

有一个主线程,我Controls在 winforms 中创建所有内容。还有第二个线程,通过事件的触发调用。由于此事件,我需要更改DataSourcey的值DataGridView。因此,会发生错误,即创建控件的主线程未尝试访问该控件。解决方案是使用方法Invoke\BeginInvoke。但是我不明白这些方法的本质以及如何在代码中实现它们。创建的第二个线程使用访问函数来dgv

private void RefreshTables()
{
    try
    {
        if(con.State == ConnectionState.Closed)
        {
            con.Open();
        }
        sql = "select rowid, * from OpenPos";
        adapOpenPos = new SQLiteDataAdapter(sql, con);
        dsOpenPos = new DataSet();
        adapOpenPos.Fill(dsOpenPos);
        dataGridView1.DataSource = dsOpenPos.Tables[0];
        dataGridView1.Columns[0].Visible = false;
        dataGridView1.Columns[15].Visible = false;
        dataGridView1.Update();
        con.Close();

        if (con.State == ConnectionState.Closed)
        {
            con.Open();
        }
        sql = "select rowid, * from ClosePos";
        adapClosePos = new SQLiteDataAdapter(sql, con);
        dsClosePos = new DataSet();
        adapClosePos.Fill(dsClosePos);
        dataGridView2.DataSource = dsClosePos.Tables[0];
        dataGridView2.Columns[0].Visible = false;
        dataGridView2.Columns[14].Visible = false;
        dataGridView2.Update();
        con.Close();
    }
    catch (Exception e)
    {
        MessageBox.Show(e.Message);
    }           
}
c#
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Best Answer
    DreamChild
    2020-06-15T18:00:00Z2020-06-15T18:00:00Z

    该方法的本质Invoke非常简单——它接受一个委托并在创建控件的线程中执行它,在该线程中Invoke。您可能已经注意到,如果从创建它们的线程以外的线程访问WinForms控件,则会抛出异常。因此,该方法Invoke在需要使用来自其他线程的控件的情况下很有用。该方法BeginInvoke执行相同的操作,但是是异步的。

    一个小的用法示例Invoke:

    private void ButtonInvoke_Click(object sender, EventArgs e)
    {
        var myThread = new Thread(ThreadFunction);
        myThread.Start(); //метод выполняется в другом потоке
    }
    
    private void ThreadFunction()
    {
        Thread.Sleep(1000);
        Action action = () => listBox1.Items.Add("value");
        // Свойство InvokeRequired указывает, нeжно ли обращаться к контролу с помощью Invoke
        if (InvokeRequired)
            Invoke(action);
        else
            action();
    }
    

    还值得注意的是async/await,在 C# 5 中添加的 , 可以绕过Invoke:

    private async void ButtonAsync_Click(object sender, EventArgs e)
    {
        listBox1.Items.Add("first");
        await Task.Run(async () =>
        {
            await Task.Delay(1000);
        });
        // этот код будет продолжен в UI потоке, 
        // и здесь нет необходимости использовать Invoke
        listBox1.Items.Add("second");
    }
    
    • 14
  2. Mark Shevchenko
    2020-06-15T18:58:49Z2020-06-15T18:58:49Z

    在窗口化的 Windows 应用程序中,有一个所谓的用户界面线程(UI thread),实际上是应用程序的主线程,最先创建。

    GetMessage它通过循环调用or函数来处理窗口消息(Windows messages)PeekMessage。如果您想对用户界面做一些事情,比如更改输入字段中的字符串,您可以向它发送一个窗口消息。

    在 WinForms 中,更改输入字段中的文本是通过赋值完成的:

    textBox1.Text = "Новый текст";
    

    但在较低级别,此代码会导致将窗口消息发送到使用 .handle的WM_SETTEXT窗口。textbox1.HandleSendMessage

    微妙之处在于,通过发送消息,您可能希望收到结果。这对 来说并不明显WM_SETTEXT,但对WM_GETTEXT您来说您肯定想知道答案。

    这意味着如果调用SendMessage,则必须等待函数完成。SendMessage如果您在只有一个线程时调用,这不是什么大问题,因为它会SendMessage调用处理程序,该处理程序通常会相当快地处理窗口消息。

    但是如果你SendMessage从另一个线程调用呢?与流行的看法相反,这不会导致错误。您可以这样做,但调用线程将停止直到SendMessage它完成。如果程序中运行了多个线程,它们想要在用户界面上改变一些东西,它们将被挂起,每个SendMessage线程将依次由主线程执行。

    在现代窗口程序中,您几乎肯定不会直接使用线程,而是使用更高级别的抽象:异步回调。在 .NET 中,您可以使用一个名为 TPL 的便捷包装器,它为您提供Task和类Task<TResult>,这样您就不必处理异步函数。

    异步函数在线程之上工作。为了让它们工作,.NET 在池中创建了多个线程,每个线程都挂在一个无限循环中并等待来自您的应用程序的信号。信号一出现,池中的某个线程就会执行您的回调函数并再次进入睡眠状态。

    这一切都很好,但通常这些小函数想要对您的用户界面做些事情。例如,您正在处理一个大文件并要求 Windows 在读取下一个 64Kb 时调用您的函数。该块被读取,Windows 唤醒线程并将您的函数传递给它执行。该函数做了有用的工作,然后想要增加进度条。它调用SendMessage可能与其他异步函数冲突。

    而如果异步函数开始互相等待,相应的线程就忙了,管理线程池的程序就不得不创建新的线程。

    这是一个问题,因为 Windows 会阻止SendMessage来自其他线程的异步调用。

    问题出现了如何解决这个问题。Windows 开发人员建议将异步函数一分为二。第一个做有用的工作,然后告诉线程池:调用第二个异步函数,但在主线程上执行。第二个函数更新进度条,由于线程池在主线程调用它,所以不会发生冲突。

    是的,界面更新仍然是顺序发生的,但所有其他异步功能完全不会相互干扰。

    为了在 UI 线程上执行函数,WinForms 开发人员提供了Invoke和BeginInvoke.

    例如,您可以像这样使用它们:

    if(con.State == ConnectionState.Closed)
    {
        con.Open();
    }
    sql = "select rowid, * from OpenPos";
    adapOpenPos = new SQLiteDataAdapter(sql, con);
    dsOpenPos = new DataSet();
    adapOpenPos.Fill(dsOpenPos);
    
    // здесь соединение можно закрыть, потому что метод `adapOpenPos.Fill`
    // уже загрузил данные
    con.Close();
    
    if (InvokeRequired)
    {
        Invoke((MethodInvoker) delegate
        {
            dataGridView1.DataSource = dsOpenPos.Tables[0];
            dataGridView1.Columns[0].Visible = false;
            dataGridView1.Columns[15].Visible = false;
            dataGridView1.Update();
        });
    }
    else
    {
        dataGridView1.DataSource = dsOpenPos.Tables[0];
        dataGridView1.Columns[0].Visible = false;
        dataGridView1.Columns[15].Visible = false;
        dataGridView1.Update();
    }
    

    在这里,参数Invoke是一个匿名委托,它实际上是一个将从 UI 线程调用的函数。

    有几种方法可以简化此代码(在 Stack Overflow 上搜索)。我会注意到主要思想:代码分别位于表单的一个方法中,InvokeRequired这Invoke是表单的一个属性和一个方法。

    Invoke如果属性InvokeRequired为真,您只需要通过调用属性更改。在调用之前,最好准备好您需要使用的所有数据,这样您就不会减慢 UI 线程。首先准备好数据,然后通过Invoke/里面的控件的属性来赋值BeginInvoke ——这是桌面应用程序异步编程的主要模式。

    • 8
  3. Alex Krass
    2020-06-15T19:22:09Z2020-06-15T19:22:09Z

    除了其他答案之外,我会尽量回答尽可能容易理解。

    通过创建一个新线程,您向它传递了一些必须在这个新线程中执行的代码。例如,通过 Task 创建时,它看起来像这样。

    new Task(() => 
    {
        //тут код, который выполнится в новом потоке    
    }).Start();
    

    另一方面,Invoke 方法用作进入主线程的窗口,否则它的声明几乎完全相同。

    new Task(() => 
    {
        //побочный поток
    
        this.Invoke(new Action(() => 
        {
            //это окошко обратно в главный поток
            //код написанный тут выполнится в главном потоке не вызывая ошибок
        }));
    
        //дальше идет снова побочный поток
    
    }).Start();
    

    更改控件的属性和调用方法必须在主线程中进行,并包装在 Invoke 中。


    我有时会使用另一种方式,它涉及主要操作之后的变化。它可能有一天会派上用场,我用它来锁定/解锁操作期间的界面。

    Task task = new Task(() => 
    { 
        //основные операции
    });
    
    task.ContinueWith(_ => 
    {
        //обновление контролов, будет вызвано после выполнения предыдущего кода
    }, TaskScheduler.FromCurrentSynchronizationContext());
    
    task.Start();
    
    • 4

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5