3

我开发了一个 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 ++;
            }

        }));


}
4

4 回答 4

8

对于 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() ) ) );
}
于 2012-04-04T04:48:51.677 回答
2

最简单的解决方案是使用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);
        }
    }
}
于 2012-04-04T04:09:05.957 回答
2

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

于 2012-04-04T03:49:07.287 回答
1

您应该将更新 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++;
            }
        });
    }
于 2012-04-04T05:32:06.760 回答