【问题标题】:How to update a list box by an asynchronous call?如何通过异步调用更新列表框?
【发布时间】:2012-04-04 03:41:13
【问题描述】:

我开发了一个 windows 窗体 c# 应用程序,我只想通过在不阻塞 GUI 窗体的情况下剥离另一个线程来更新主窗体中的列表框中的项目。 由于线程无法访问列表框等表单实体,因此我想到了使用委托。 下面的代码显示了我如何使用委托来执行该任务,但它会阻止 GUI 表单。所以我只想将它转换为一个异步委托,它更新列表框而不阻塞 GUI 表单

委托声明

 delegate void monitoringServiceDel();

调用代理

new monitoringServiceDel(monitoringService).BeginInvoke(null, null);

委托方法实现

private void monitoringService()
{
        this.listEvents.Invoke(new MethodInvoker(delegate()
        {
            int i = 0 ;
            while (i<50)
            {

                listEvents.Items.Add("count :" + count++);
                Thread.Sleep(1000);
                i ++;
            }

        }));


}

【问题讨论】:

    标签: c# delegates


    【解决方案1】:

    对于 Win Forms,您需要使用 Control 的 Invoke 方法:

    在拥有控件的线程上执行指定的委托 底层窗口句柄

    基本场景是:

    类似的东西:

    var bw = new BackgroundWorker();
    bw.DoWork += (sender, args) => MethodToDoWork;
    bw.RunWorkerCompleted += (sender, args) => MethodToUpdateControl;
    bw.RunWorkerAsync();
    

    这应该会让你朝着正确的方向前进。

    编辑:工作示例

    public List<string> MyList { get; set; }
    
    private void button1_Click( object sender, EventArgs e )
    {
        MyList = new List<string>();
    
        var bw = new BackgroundWorker();
        bw.DoWork += ( o, args ) => MethodToDoWork();
        bw.RunWorkerCompleted += ( o, args ) => MethodToUpdateControl();
        bw.RunWorkerAsync();
    }
    
    private void MethodToDoWork()
    {
        for( int i = 0; i < 10; i++ )
        {
            MyList.Add( string.Format( "item {0}", i ) );
            System.Threading.Thread.Sleep( 100 );
        }
    }
    
    private void MethodToUpdateControl()
    {
        // since the BackgroundWorker is designed to use
        // the form's UI thread on the RunWorkerCompleted
        // event, you should just be able to add the items
        // to the list box:
        listBox1.Items.AddRange( MyList.ToArray() );
    
        // the above should not block the UI, if it does
        // due to some other code, then use the ListBox's
        // Invoke method:
        // listBox1.Invoke( new Action( () => listBox1.Items.AddRange( MyList.ToArray() ) ) );
    }
    

    【讨论】:

    • 我已将您的编码包含在按钮单击事件中。它将成功调用 MethodToDoWork,并且一旦尝试更新 MethodToUpdateControl 内的列表框,它将通过在 Application.Run(new FormMain()); 处说“TargetInvocationException was unhandled”来生成异常;在 program.cs 中
    • 救了我的命。完美运行
    【解决方案2】:

    如果您正在修改 UI 元素,那么您将不得不阻塞 UI 线程。如果项目突然出现或需要在添加每个项目之间进行处理,那么您可能需要考虑在幕后运行处理(通过backgroundworkerTask)。但是,如果您只是获取数据并填充列表,则需要使用 UI 线程。

    【讨论】:

    • 在我添加项目时 (listEvents.Items.Add("count :" + count++);) 我可以在委托函数中进行异步回调以更新列表吗?
    • 谁创建了面板和列表?如果是 UI,那么您将收到上下文错误。但是,我想您可以在后台工作人员中创建面板和列表,然后将其传递给 UI 以由主线程插入?
    • 对不起,我删除了评论。您的表单中已准备好两个面板。您使用 BackgroundWorker 来获取填充 ListBox 的数据。一旦 BackgroundWorker 完成并且您拥有所有数据,您就可以访问您的控件而不会出现问题和跨线程。因此,您只需交换可见的面板并设置 ListBox 内容。
    • @PeekaySwitch 这和我说的有什么不同(除了更明确之外?)一旦你有了数据,你必须阻塞 UI 线程?
    • @JustinPihony 我不会完全称它为阻塞 UI 线程。 BackgroundWorker 离开 UI 响应,面板可见性交换 + ListBox 填充将需要 0.001 秒才能完成,并且不会冻结 UI。
    【解决方案3】:

    最简单的解决方案是使用BackgroundWorker 控件,结合两个Panels。这个想法是在表单加载时在前台有一个面板Visible,并在其中有一个ImageBox,它播放一个简单的加载gif。 ListBox 将位于默认情况下不可见的另一个面板内,并且将位于第一个面板的正后方。

    加载表单后,启动您的BackgroundWorker 并完成您必须执行的任何数据检索或更新,一旦任务完成,将数据设置在您的 ListBox 中,只需带上ListBox 面板并制作它可见。

    这样您就可以半异步地加载您的 ListBox,而在添加每个项目后它不会更新。您可以随时使用此技术,而不仅仅是在表单加载时!

    这是一个代码示例:

    namespace AsyncForm
    {
        public partial class Form1 : Form
        {
    
            private List<String> collectionItems = new List<String>();
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
            {
                for (int i = 0; i < 20; i++)
                {
                    ((List<String>)e.Argument).Add("Something " + i);
                    System.Threading.Thread.Sleep(200);
                }
            }
    
            private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                listBox1.Items.AddRange(collectionItems.ToArray());
                listBox1.Visible = true;
                pictureBox1.Visible = false;
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                backgroundWorker1.RunWorkerAsync(collectionItems);
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      你应该将更新 UI 的函数和长时间的进程分开。

      处理 UI 逻辑..

          private void UpdateUI(string item) 
          {
              if (Thread.CurrentThread.IsBackground) 
              {
                  listEvents.Dispatcher.Invoke(new Action(() => //dispatch to UI Thread
                  {
                      listEvents.Items.Add(item);
                  }));
              }
              else
              {
                  listEvents.Items.Add(item);
              }
          }
      

      使用TaskParallel进行异步处理

          private void Dowork()
          {
              Task task = Task.Factory.StartNew(() =>
              {
                  int i = 0;
                  while (i < 10)
                  {
                      Thread.Sleep(1000);
                      UpdateUI(i.ToString());
                      i++;
                  }
              });
          }
      

      【讨论】:

      • 如果我删除了“调度程序”,对我有用。我没有那个方法
      猜你喜欢
      • 2021-08-27
      • 1970-01-01
      • 1970-01-01
      • 2018-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多