一緒に投げたc#Windowsフォームアプリがあります。それはかなり簡単です:\
入力:
アプリは、ソースフォルダー内のテキストファイルを検索して、入力されたテキスト文字列を探します。文字列が見つかった場合は、そのファイルと同じ名前の画像ファイルを宛先フォルダにコピーします。ただし、整数入力に基づいてこれを何度も実行します。
だから私はボタンを持っています、そしてボタンクリックイベントで私は呼び出します
ProcessImages(tbDID.Text, tbSource.Text, tbDest.Text, comboBoxNumberImages.SelectedItem.ToString());
これは:
private void ProcessImages(string DID, string SourceFolder, string DestFolder, string strNumImages)
{
int ImageCounter = 0;
int MaxImages = Convert.ToInt32(strNumImages);
DirectoryInfo di = new DirectoryInfo(SourceFolder);
foreach (FileInfo fi in di.GetFiles("*.txt"))
{
if (fi.OpenText().ReadToEnd().Contains(DID))
{
//found one!
FileInfo fi2 = new FileInfo(fi.FullName.Replace(".txt", ".tif"));
if (fi2.Exists)
{
try
{
tbOutput.Text += "Copying " + fi2.FullName + " to " + tbDest.Text + "\r\n";
fi2.CopyTo(tbDest.Text + @"\" + fi2.Name, true);
tbOutput.Text += "Copying " + fi.FullName + " to " + tbDest.Text + "\r\n";
fi.CopyTo(tbDest.Text + @"\" + fi.Name, true);
ImageCounter++;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
if (ImageCounter >= MaxImages)
break;
}
}
プロセスは正常に実行されますが、ファイルがコピーされるにつれて、フォームのテキストボックスが進行状況とともに更新されます。基本的に、フォームの実行中はフォームが空白になり、フォームの実行が終了すると、出力はテキストボックスに表示されます。 BackgroundWorkerを実装して、実行中にUIを更新するようにしたいと思います。
私は例を調べましたが、実際にはそれらに従っていません。完了率の値がありません。繰り返しごとに.Textが変更され、表示されるようにしたいだけです。実際のコピーアクションを必ずしも別のスレッドに配置する必要はないと思います。メインのUIスレッドとは別に実行する必要があるようです。多分私はこれを完全に複雑にしすぎています...誰かが私を正しい方向に押すことができますか?ありがとう!
バックグラウンドワーカーを使用する場合は、ReportProgressメソッドを使用して、処理されたレコード数などの任意の整数を返すことができます。パーセンテージである必要はありません。次に、ProgressChangedハンドラーで、テキストボックスを更新できます。例えば。
int count = e.ProgressPercentage;
textBox1.Text = string.Format("{0} images processed.", count);
バックグラウンドワーカーを使用したくない場合は、ループ内でApplication.DoEvents()を呼び出すことができます。これにより、UIはそれ自体を更新し、ユーザーアクションに応答する機会が与えられます。ただし、注意してください。プログラムの速度が大幅に低下するため、100回の反復ごとにのみ呼び出すことをお勧めします。
あなたはバックグラウンドワーカーと正しい方向に進んでいます。これを行う方法を示すために私がまとめた例を次に示します。 Form1を使用して新しいWindowsアプリを作成します。 label1、backgroundWorker1、button1、button2の4つのコントロールを追加します。次に、このコードビハインドを使用します。次に、ReportProgress userStateを使用して、必要に応じてメインスレッドにレポートを返すことができます。この例では、文字列を渡しています。その後、ProgressChangedイベントハンドラーがUIスレッド上にあり、テキストボックスを更新します。
public partial class Form1 : Form
{
int backgroundInt;
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = e.UserState as string;
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
backgroundInt = 1;
while (backgroundWorker1.CancellationPending == false)
{
System.Threading.Thread.Sleep(500);
backgroundWorker1.ReportProgress(0,
String.Format("I found file # {0}!", backgroundInt));
backgroundInt++;
}
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void button2_Click(object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
}
長時間実行されるファイル処理ループでウィンドウメッセージを処理することを許可していないため、UIは更新されません。 WinFormsアプリは、メインスレッドのメッセージキューで処理されるWM_Paintメッセージに応答して再描画します。
最も簡単な解決策は、UIを強制的に更新することです。ループ内のテキストボックスを変更した後、フォームでUpdate()を呼び出してみてください。
アプリは引き続きUIフリーズ(マウスクリックに反応しないなど)になりますが、少なくとも画面に進行状況メッセージが表示されるはずです。表示を更新するだけで本当に必要な場合は、ここで停止します。
次のレベルのソリューションは、アプリケーションがファイル処理ループで保留中のウィンドウメッセージを処理できるようにすることです。ループ内で(form.Updateの代わりに)Application.DoEvents()を呼び出します。これにより、フォームがテキスト出力の更新で再描画され、UIのフリーズがなくなります。アプリはマウスとキーボードのアクティビティに応答できます。
ただし、ここでは注意してください-現在のアクティビティの進行中にユーザーが現在のアクティビティを開始したボタンをクリックする可能性があります-再入可能。再入可能を防ぐために、少なくとも、長時間実行されるファイル処理を開始するメニューまたはボタンを無効にする必要があります。
解決策の第3レベルは、ファイル処理にバックグラウンドスレッドを使用することです。これにより、注意が必要な多くの新しい問題が発生し、多くの場合、スレッドはやり過ぎです。ファイル処理が行われている間、ユーザーがアプリで他のことを実行できないようにする場合は、ファイル処理をバックグラウンドスレッドにプッシュする意味はあまりありません。
(1)ProcessImagesメソッド呼び出しをラップするデリゲートを作成し、(2)デリゲートを使用して呼び出しを開始し、(3)ProcessImagesメソッドからテキストボックスを更新する場合は、クロスヘッド操作を確認して作成します。メインスレッドから更新を行うようにしてください。
delegate void ProcessImagesDelegate(string did, string sourceFolder, string destFolder, string strNumImages);
private void ProcessImages(string DID, string SourceFolder, string DestFolder, string strNumImages)
{
// do work
// update textbox in form
if (this.textBox1.InvokeRequired)
{
this.textBox1.Invoke(new MethodInvoker(delegate() { this.textBox1.Text = "delegate update"; }));
}
else
{
this.textBox1.Text = "regular update";
}
// do some more work
}
public void MyMethod()
{
new ProcessImagesDelegate(ProcessImages).BeginInvoke(tbDID.Text, tbSource.Text, tbDest.Text, comboBoxNumberImages.SelectedItem.ToString(), null, null);
}