###前提・実現したいこと
C#のマルチタスクとタスクの入れ子について基本的なところを質問させて頂きたく、よろしくお願い致します。
具体的な例として、windows上でマルチタスクで異なるPCのフォルダ間で複数ファイルの同期を取るシステムで
述べさせて頂きます。
PC1とPC2間,PC1とPC3間で同期を取るものとし、以下のようなものです。
[PC1とPC2間]
・PC1下のフォルダ1~3下のファイルが、PC2下のフォルダ1~3下に同期されコピーされる。
・フォルダ1下にはファイル1,フォルダ2下にはファイル2,フォルダ3下にはファイル3がある。
・ファイル1は500ms,ファイル2は1000ms,ファイル3は1500ms周期でそれぞれrobocopyで同期される。
[PC1とPC3間]
・PC1下のフォルダ4~6下のファイルが、PC3下のフォルダ4~6下に同期されコピーされる。
・フォルダ4下にはファイル4,フォルダ5下にはファイル5,フォルダ6下にはファイル6がある。
・ファイル4は500ms,ファイル5は1000ms,ファイル6は1500ms周期でそれぞれrobocopyで同期される。
PC1とPC2間の同期用にタスクA,PC1とPC3間の同期用にタスクBを起動します。
さらに、robocopyはwindowsプロセスを用いて実行させるため、そのためのタスクをタスクA,Bそれぞれから
子タスクとして起動します。
タスクA,B内では各ファイルの前回同期からの経過時刻を計っており、それが同期周期に達したら同期を取るように
させています。
上記実装にあたり、質問させて頂きたいことは以下です。
その他、指摘事項ありましたら併せてご指摘のほどお願い致します。
Q1.各タスクの同期・非同期をawaitを用いて正しく実装できているか。
なるたけフォルダ同期アプリのCPU負荷を下げ、かつ、同期周期からの遅延を少なくして実装したいので
そのような観点から同期・非同期の使い方で誤っている箇所があればご指摘をお願い致します。
※コード部に★Q1.1~★Q1.6に具体的な内容を記載しましたのでご回答をよろしくお願い致します。
(基本的な部分の認識が十分でないので質問内容自体にも認識の誤りがあるかも知れませんがご容赦願います)
Q2.各ファイルの実測した同期時間が同期周期から遅延してしまう場合、どのような解決策を用いればよいか。
上記で述べました例ではファイル数が少ない場合、同期周期の遅延は発生しないのですが、
例えば、同期先のPCが10個あり、フォルダ同期タスクを10個生成し、かつ、各フォルダ下のファイル数が万オーダーで
あるような場合、現状のコードでは30秒の同期周期に対して実測周期が60秒程度かかる場合が頻発するなど、
遅延時間が膨大になってきてしまっています。
別の方法として、タイマー:System.Threading.Timeを用いてみました。 10個のフォルダ同期タスク下でそれぞれ同期周期の種類数分のタイマー (上の例の場合500,1000,1500msのタイマー)を起動し、同期周期の遅延が少なくなるよう試みましたが だめでした。 アプリ全体として10個×同期周期の種類数分のタイマーが生成されるということで、種類が少ないうちは 遅延が発生しませんが、一定数を超えるとCPU負荷が膨大になりアプリが動作しなくなってしまいました。 何かこれ以外でよい方法があればご教授お願い致します。
以下にコードを記載しますので、よろしくご回答のほどお願い致します。
###該当のソースコード
/* ◆フォルダ同期アプリクラス /
public partial class CFolderSyncApp : Form
{
/ ◇メンバ変数 */
private DateTime[] m_TaskAPreTime = new DateTime[3]; // ファイル1,2,3の前回の同期時刻
private DateTime[] m_TaskBPreTime = new DateTime[3]; // ファイル4,5,6の前回の同期時刻
private int[] m_TaskACycle = {500, 1000, 1500}; // ファイル1,2,3の同期周期
private int[] m_TaskBCycle = {500, 1000, 1500}; // ファイル4,5,6の同期周期
/* ◇フォルダを同期する */
private async void SyncFolder()
{
var task_a = FolderSyncTaskA();
var task_b = FolderSyncTaskB();
await Task.WhenAll(task_a, task_b); // タスクA,タスクBを並列実行する
}
/* ◇フォルダ同期タスクA(PC1とPC2間) */
private Task FolderSyncTaskA()
{
return Task.Run(async () => {
while (true) {
SyncFileA();
await Task.Delay(500); // 500ms周期でファイルを同期する(PC1とPC2間)
// ★Q1.1 awaitの有り無しでそれぞれどのような動きになりますか?
// ★Q1.2 await Whenall()でFolderSyncTaskA()が待たれ、その中のawaitであるので
// 本タスク内で500msを同期的に待つという認識で正しいでしょうか?
// ★Q1.3 結論としてawaitは要りますか?
}
});
}
/* ◇フォルダ同期タスクB(PC1とPC3間) */
private Task FolderSyncTaskB()
{
return Task.Run(async () => {
while (true) {
SyncFileB();
await Task.Delay(500); // 500ms周期でファイルを同期する(PC1とPC3間)
}
});
}
/* ◇ファイルを同期する(PC1とPC2間) */
private void SyncFileA()
{
for (int i=0; i < 3; i++) {
double ms = (DateTime.Now - m_TaskAPreTime[i]).TotalMilliseconds; // 前回の同期時刻との差[ms]
if (ms > m_TaskACycle[i]) { if (i == 0) { // PC1下のファイル1を500ms周期でPC2下に同期する // ★Q1.4 RobocopyTask()の前にawaitの有り無しでそれぞれどのような動きになりますか? // ★Q1.5 結論としてRobocopyTask()の前にawaitは要りますか? CCommon common = new CCommon(); common.RobocopyTask("(PC1のフォルダ1のパス)", "(PC2のフォルダ1のパス)", "ファイル1"); } else if (i == 1) { // PC1下のファイル2を1000ms周期でPC2下に同期する CCommon common = new CCommon(); common.RobocopyTask("(PC1のフォルダ2のパス)", "(PC2のフォルダ2のパス)", "ファイル2"); } else if (i == 2) { // PC1下のファイル3を1500ms周期でPC2下に同期する CCommon common = new CCommon(); common.RobocopyTask("(PC1のフォルダ3のパス)", "(PC2のフォルダ3のパス)", "ファイル3"); } m_TaskAPreTime[i] = DateTime.Now; } }
}
/* ◇ファイルを同期する(PC1とPC3間) */
private void SyncFileB()
{
for (int i=0; i < 3; i++) {
double ms = (DateTime.Now - m_TaskBPreTime[i]).TotalMilliseconds; // 前回の同期時刻との差[ms]
if (ms > m_TaskBCycle[i]) { if (i == 0) { // PC1下のファイル4を500ms周期でPC3下に同期する CCommon common = new CCommon(); common.RobocopyTask("(PC1のフォルダ4のパス)", "(PC3のフォルダ4のパス)", "ファイル4"); } else if (i == 1) { // PC1下のファイル5を1000ms周期でPC3下に同期する CCommon common = new CCommon(); common.RobocopyTask("(PC1のフォルダ5のパス)", "(PC3のフォルダ5のパス)", "ファイル5"); } else if (i == 2) { // PC1下のファイル6を1500ms周期でPC3下に同期する CCommon common = new CCommon(); common.RobocopyTask("(PC1のフォルダ6のパス)", "(PC3のフォルダ6のパス)", "ファイル6"); } m_TaskBPreTime[i] = DateTime.Now; } }
}
};
/* ◆共通クラス /
public class CCommon
{
/ ◇メンバ変数 */
private bool m_IsExit; // windowsプロセスが終了したか[true:終了/false:未終了]
/* ◇robocopyをwindowsプロセスで実行するタスク */
public Task RobocopyTask(string src_folder, string dst_folder, string file)
{
return Task.Run(() => {
// ファイル単位でrobocopyをwindowsプロセスで実行するための設定をする
Process process = new Process();
process.StartInfo.FileName = "robocopy.exe";
process.StartInfo.Arguments = src_folder + " " + dst_folder + " /IF " + file;
// プロセス終了ハンドラを登録する
process.Exited += new EventHandler(Process_Exit);
process.EnableRaisingEvents = true;
m_IsExit = false;
// robocopyプロセスを開始し、10ms周期でプロセスが終了したか確認する // ★Q1.6 Thread.Sleep()よりawait Task.Delayを用いるべきでしょうか?(その場合「Task.Run(async()」に変える) // 両者でそれぞれどのような動きになりますか? process.Start(); while (true) { Thread.Sleep(10); if (m_IsExit) { break; } } // プロセスを閉じる process.Exited -= new EventHandler(Process_Exit); process.Close(); });
}
/* ◇プロセス終了時呼ばれるイベントハンドラ */
private void Process_Exit(object sender, EventArgs e)
{
m_IsExit = true;
}
};
###補足情報(言語/FW/ツール等のバージョンなど)
VisualStudio2015 .Net4.6 C#
回答3件
あなたの回答
tips
プレビュー