我在 C# Windows 窗体上编写了一个程序,用于在 MATLAB 上保存文件。 MATLAB 上保存数据的功能运行时间太长,因此我决定使用进度条来向用户显示完成该过程需要多长时间。我附上我的代码,在某些情况下运行正常,但出现错误 "System.IndexOutOfRangeException: '复制内存时检测到可能的 I/O 竞争条件。默认情况下,I/O 包不是线程安全的。在多线程应用程序中,必须以线程安全的方式访问流,例如线程- 由 TextReader 或 TextWriter 的 Synchronized 方法返回的安全包装器,这也适用于 StreamWriter 和 StreamReader 等类。'" 在某些情况下。 我想让main函数在主线程上,同时在另一个线程上有进度条更新,当然我希望文件一个一个地存储,进度条填充一个一个地进行。事实上,我想避免多线程,因为整个程序是以单线程方式编写的,只需另一个线程来更新每个进度条值。下面是我的代码。我很高兴能得到您的大力帮助。`公共课 SaveToMatlab { ProgressBar 进度条 = new ProgressBar();
private void BtnSaveToMatlab_Click(object sender, EventArgs e)
{
ResetProgressBars();
if (!backgroundWorker.IsBusy)
{
backgroundWorker.RunWorkerAsync();
}
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < tableLayoutPanel.Controls.Count; i++)
{
var flp = tableLayoutPanel.Controls[i] as FlowLayoutPanel;
if (flp != null && flp.Controls.OfType<CheckBox>().Any(x => x.Checked))
{
progressBar = flp.Controls.OfType<ProgressBar>().FirstOrDefault();
if (progressBar != null)
{
var directory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "MatlabOutput");
fileList[i].SaveLogToMatWorkSpace(Path.Combine(directory, $"matlab_{fileList[i].GetIdentifier()}.mat"), backgroundWorker);
}
}
}
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = progressBar.Value + e.ProgressPercentage > 100 ? 100 : progressBar.Value + e.ProgressPercentage;
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Operation was canceled.");
}
else if (e.Error != null)
{
MessageBox.Show("An error occurred: " + e.Error.Message);
}
else
{
MessageBox.Show("Operation completed successfully.");
}
}
}
公开课 其他类 { 公共无效SaveLogToMatWorkSpace(字符串文件名,BackgroundWorker工人) { if (indexs.Count > 0) { 双增量 = 100 / (双)(indices.Count - 1);
for (int i = 0; i < indices.Count; i++)
{
if (indices[i].Count > 0)
{
//... My code to save in MATLAB...
}
int progress = (int)Math.Ceiling(increment);
worker.ReportProgress(progress);
}
new MatFileWriter(fileName, mlList, true);
}
}
} `
如果您的慢速方法是异步的,您只需编写一个循环即可:
public async Task DoWork(){
foreach(var file in filelist){
await file.SaveLogToMatWorkSpace(...);
UpdateProgressBar();
}
}
如果
SaveLogToMatWorkSpace
在 UI 线程上所做的工作可以忽略不计,这应该保持 UI 响应。
如果慢速方法不是异步的,您实际上没有选择,您需要使用另一个线程。 UI 线程一次只能做一件事,如果它运行缓慢的方法,它无法对用户输入做出反应或自行绘制,从而导致“应用程序已停止响应错误”。
避免多线程的大部分危险的一种方法是在后台线程处理时显示模式对话框。这有助于防止同时运行任何“您的”代码,因此有助于避免许多线程问题,同时仍然允许 UI 自行绘制并处理输入。这可能看起来像:
public async void OnButtonClick()
{
var progress = new Progress<int>();
var progressWindow = new MyProgressWindow(progress);
var task = StartWork(progressWindow, progress);
// ShowDialogwill prevent the user from interacting with anything other than the ProgressWindow.
progressWindow.ShowDialog();
try
{
var result = await task;
}
catch (Exception ex)
{
// handle errors
}
}
public async Task<MyResult> StartWork(Window window, IProgress<int> progress)
{
var result = await Task.Run(() => DoWork(progress));
// Automatically close the progress window when the work is done
// Use `async/await` to continue on the UI thread once DoWork has completed
window.Close();
return result;
}
public MyResult DoWork(IProgress<int> progress)
{
// Do actual work on a background thread
// use progress.Report(i); to report progress
}
MyProgressWindow
将是一个包含进度条的简单窗口,仅此而已。不要忘记禁用窗口上的关闭按钮、设置所有者等。监听
progress.ProgressChanged
事件来更新进度条。您可以在没有模式对话框的情况下执行相同的操作,但是您需要确保在 DoWork
正在进行时用户可以执行的任何操作都是安全的,或者禁用任何不安全的操作。因此显示模式对话框通常要简单得多。
progress类是线程安全的,并且是为此类情况明确设计的,但您无法访问DoWork
中UI的任何其他部分。在
StartWork
中提取任何需要的信息,并将它们作为参数传递给 DoWork
。您还可以考虑添加取消按钮,请参阅CancellationTokenSource