1

在我的应用程序中,我有一个由进度条和文件名组成的队列下载列表。当用户单击按钮时,文件名和进度条被实例化并添加到队列中。文件一次异步下载一个。我想要做的是将等待下载的文件的所有进度条保持为黄色,然后在下载时变为绿色,然后在完成时变为蓝色。如果我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;
    }
 }

队列栏片段

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
        }
 }

我也只是尝试创建一个后台工作人员来创建队列项并异步添加控件,但这不起作用,因为拆分容器是在不同的线程上创建的。

4

1 回答 1

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 ) { ... }

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

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

于 2012-09-30T21:30:46.313 回答