我本来是用Socket下载文件,想做个进度条,对客户有个提示,避免长时间无聊的等待。
代码如下:
private void setPrograss(int alllen, int curlen)
{
System.Drawing.Size grs = new System.Drawing.Size(alllen, curlen);
progressBar2.Invoke(new EventHandler(setPrograss), new object[] { grs, EventArgs.Empty });
//执行到这行就停止执行了,至少等待了五六分钟没反应
}
public delegate void degPrograss(int allLen, int curLen);
private void setPrograss(object data, EventArgs e)
{
System.Drawing.Size grs = (System.Drawing.Size)data;
if (progressBar2.Maximum == 1)
{//实际长度不可能为1
progressBar2.Maximum = grs.Width;
}
progressBar2.Value = grs.Height;
}
现在问题是执行到
progressBar2.BeginInvoke(new EventHandler(setPrograss), new object[] { grs, EventArgs.Empty });
这句时,程序就不运行了,查网上说换BeginInvoke,结果是运行了,但进度条也不显示呀!
很急,请各位帮忙看看,要怎样写才对?
System.ComponentModel命名空间下,已经有了一个ProgressChangedEventHandler。
你自己定义的事件调用,滥用了事件的通用模式,代码比较难理解,还容易造成误解。最好直接用ProgressChangedEventHandler。
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
// InitializeComponent();
button.Click += Button_Click;
this.Controls.AddRange(new Control[] { progressBar2, button });
}
private void Button_Click(object sender, EventArgs e)
{
button.Enabled = false;
ThreadPool.QueueUserWorkItem(state =>
{
for(int i = 1; i <= 100; i++)
{
OnProgressChanged(this, new ProgressChangedEventArgs(i, null));
Thread.Sleep(50); // 模拟Socket下载文件
}
});
}
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
if(progressBar2.InvokeRequired)
{
progressBar2.Invoke((ProgressChangedEventHandler)OnProgressChanged, sender, e);
}
else
{
progressBar2.Value = e.ProgressPercentage;
button.Enabled = progressBar2.Value == 100;
}
}
ProgressBar progressBar2 = new ProgressBar() { Dock = DockStyle.Fill, Maximum = 100};
Button button = new Button() { Text = "开始", Dock = DockStyle.Bottom };
}
}
另外一种选择是使用Winform的BackgroundWorker。BackgroundWorker已经把UI Invoke帮我们做好了。
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
// InitializeComponent();
button.Click += Button_Click;
this.Controls.AddRange(new Control[] { progressBar2, button });
backgroundWorker.DoWork += delegate
{
for (int i = 1; i <= 100; i++)
{
backgroundWorker.ReportProgress(i);
Thread.Sleep(50); // 模拟进度
}
};
backgroundWorker.ProgressChanged += OnProgressChanged;
}
private void Button_Click(object sender, EventArgs e)
{
button.Enabled = false;
backgroundWorker.RunWorkerAsync();
}
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar2.Value = e.ProgressPercentage;
button.Enabled = progressBar2.Value == 100;
}
BackgroundWorker backgroundWorker = new BackgroundWorker() { WorkerReportsProgress = true};
ProgressBar progressBar2 = new ProgressBar() { Dock = DockStyle.Fill, Maximum = 100};
Button button = new Button() { Text = "开始", Dock = DockStyle.Bottom };
}
}
其实跟你贴出的代码没啥大的关系,而是跟你的“客户调用”代码有关。当UI主线程循环调用它,不切换、不释放,那么进度条控件根本得不到刷新显示的机会。
随便写一个例子:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
for (var i = 0; i <= 100; i++) //循环中你可以拖动窗口测试UI并发逻辑
{
await Task.Delay(300);
await SetProgress(i);
}
}
private async Task SetProgress(int n)
{
await Task.Yield();
this.progressBar1.Value = n;
}
}
}
你可以看到,过程要异步操作,让 UI 线程得以释放、切换,才能看出并发效果来。
或者更简单直接点,直接写为这样的吧:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
for (var n = 0; n <= 100; n++) //循环中你可以拖动窗口测试UI并发逻辑
{
await Task.Delay(300);
this.progressBar1.Value = n;
}
}
}
}
你可以看到,this.progressBar1.Value = n; 这一句是同步代码,相当于你题目中的调用逻辑。这一句是阻塞 UI 线程的。但 是小蜜蜂论坛发帖机因为有了 await Task.Delay()语句,那么这句同步阻塞代码被异步调用它了,所以它可用肉眼可能到显示了并发的效果。