实例化异步运行的进度条并使用线程安全调用对其进行修改

时间:2012-09-30 21:26:49

标签: c# .net winforms

在我的应用程序中,我有一个队列下载列表,其中包含进度条和文件名。当用户单击按钮时,将实例化文件名和进度条并将其添加到队列中。文件一次下载一个并异步下载。我想要做的是将等待下载的文件的所有进度条保持黄色,然后在下载时变为绿色,然后在完成后变为蓝色。如果我在自定义进度条的构造函数中有CheckForIllegalCrossThreadCalls = false;,它目前有效。我想看看是否有办法对进度条进行线程安全更改。

我将每个队列项设置为对象。当按下按钮并在队列项构造函数中创建进度条时,队列项对象是从主表单代码(Form1.cs)创建的,这可能是我的问题开始的地方。下载是通过队列项对象中的函数启动的。

队列项目片段

 public class QueueItem
 {
    public bool inProgress;
    public QueueBar bar;

    public QueueItem(args)
    {
         bar = new QueueBar();
         inProgress = false;
         // handle arguments
    }

    public void Download()
    {
        // process info
        WebClient client = new WebClient();
        client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
        client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
        client.DownloadFileAsync(url, @savePath);
    }

    private long lastByte = 0;
    private long newByte = 0;
    private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
        percentValue = e.ProgressPercentage;
        bar.Value = e.ProgressPercentage;
        newByte = e.BytesReceived;
    }

    private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
    {
        // change bar color
        bar.Value = 100;
    }
 }

Queue Bar Snippet

public class QueueBar : ProgressBar
{
    // variables

    public QueueBar()
    {
        this.SetStyle(ControlStyles.UserPaint, true);     
        // initialize variables
    }

    // function to change text properties
    // function to change color

    protected override void OnPaint(PaintEventArgs e)
    {
        // painting
    }
}

主要功能代码段

public partial class Form1 : Form
{
    private List<QueueItem> qItems;
    private BackgroundWorker queue;

    private void button_Click(object sender, EventArgs e)
    {
         // basic gist of it
        qItems.Add(new QueueItem(args));
        Label tmpLabel = new Label();
        tmpLabel.Text = filename;
        tmpLabel.Dock = DockStyle.Bottm;
        splitContainerQueue.Panel2.Controls.Add(tmpLabel);
        splitContainerQueue.Panel2.Controls.Add(qItems[qItems.Count - 1].bar);

        if (!queue.IsBusy) { queue.RunWorkerAsync(); }
    }

    private void queue_DoWork(object sender, DoWorkEventArgs e)
    {
        while (qItems.Count > 0)
        {
            if (!qItems[0].inProgress && qItems[0].percentValue == 0)
            {
                qItems[0].inProgress = true;
                qItems[0].Download();
            }
            // else if statements
        }
 }

我还尝试创建一个后台工作程序来创建队列项并异步添加控件但由于拆分容器是在另一个线程上创建的,因此无效。

1 个答案:

答案 0 :(得分:0)

您无法安全地从另一个线程调用UI控件(在您的UI线程上创建) - 您需要使用InvokeRequired / BeginInvoke()进行此类调用。致电BeginInvoke()时,您将通过代表;像这样的东西(只是一些示例代码,你的看起来会略有不同):

private void SomeEventHandler ( object oSender, EventArgs oE )
{
    if ( InvokeRequired )
    {
        MethodInvoker oDelegate = (MethodInvoker) delegate
        {
            SomeEventHandler ( oSender, oE );
        };

        BeginInvoke ( oDelegate );
        return;
    }
    else
    {
        // already on the correct thread; access UI controls here
    }
}

您也无法在UI线程之外创建进度条 - 您需要在UI中创建所有控件,然后如果需要从队列项中访问这些进度条,则必须传入对进度条的引用。当您尝试访问进度条时,您将

if ( bar.InvokeRequired ) { ... }

确定您是否尝试从正确的线程调用它。

造成这种混乱的原因是控件通过消息处理许多属性更新,并且这些消息必须以正确的顺序同步传递。确保这一点(没有一些非常复杂的编码)的唯一方法是在线程运行消息泵的同一线程上创建所有控件。