質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.59%

C#のマルチタスクとタスクの入れ子などについて

解決済

回答 3

投稿

  • 評価
  • クリップ 3
  • VIEW 4,577

score 19

前提・実現したいこと

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#

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • ishi9

    2016/11/25 14:36 編集

    同期の周期は実測値に応じて、いくら遅れても大丈夫ということですか?であれば、stopwatch等でタスクの時間を計測しておいて、かかった時間に応じてdelayするのでもいいと思います。あるいは計測などせずコピータスク完了後、一律で1秒待つ。つまり同期周期はコピーにかかった時間+1秒にしてしまう

    キャンセル

  • matsu1

    2016/11/25 15:36

    同期の周期は実測値に応じて、いくら遅れても大丈夫ということですか?→先ほど記載しましたように、同期周期が500msや1000msとプログラムで組んでいるものは実測の同期時間も±20%、すなわち500msであれば400~600ms程度に、1000msであれば800~1200ms程度に。10秒であれば±10%、すなわち9~11秒程度になっていてほしいと考えています。よろしくお願い致します。

    キャンセル

  • ishi9

    2016/11/25 15:39

    失礼しました。では実処理時間を高速化するしかありませんね。そちらに関しては回答してみます。

    キャンセル

回答 3

checkベストアンサー

+2

こんにちは。
質問の内容はひとまず置いておいて、少し気になったことがあるので回答します。
疑問点に対する直接の回答でなくて申し訳ないですが……。


まず最初に。非同期や並列で「解決できる事柄」を勘違いしているように思います。
並列にすることでパフォーマンスが出せるというのは、複数のコアを持つCPUがあり、複数の処理を文字通り「同時に」処理できることが前提となります。
では、ファイルのコピーやネットワークはどうでしょうか?RAID構成でもなければHDDは基本的に1台でしょうし、ネットワークもほとんどの環境で1本でしょう。ということは、複数のタスクを同時に処理しようとすればするほど1本のパイプを圧迫していくため、ある段階から詰まったように遅くなっていくはずです。
並列で特にパフォーマンスを得られる箇所は「CPUによる計算処理」、そしてそもそもが並列である「分散処理」環境だけなのです。


質問者さんのこのような問題の解決手法として、「不定期(定期)に実行される多数の処理を一度一つの実行キューに並べ、それを順番に実行していく」という実装方法を提案します。このようなデザインのことを「スケジューラー」といいます。
スケジューラーに処理を登録(予約)すると、スケジューラーがそれを勝手に順次実行していくので、使用者側では定期的に処理をスケジューラーに投げるだけでよくなります。

お節介ですが、ちょうど少し前に似たような問題に適用できるスケジューラーの汎用実装を作ったものがあったので、もしよろしければこちらをお使いください。(100行くらいあるけど回答欄に貼ってもいいんですかね……?)

(12/13 コードを更新)

public class ActionQueueScheduler
{
    private readonly ConcurrentQueue<Func<Task>> queue;

    private int isRunning;

    private Func<Task> immediate;

    public ActionQueueScheduler()
    {
        this.queue = new ConcurrentQueue<Func<Task>>();
    }

    public Task Add(Action action)
        => this.Add(() => { action(); return default(object); });

    public Task<T> Add<T>(Func<T> action)
    {
        var tcs = new TaskCompletionSource<T>();
        this.queue.Enqueue(() => CreateTask(action, tcs));
        this.Run();
        return tcs.Task;
    }

    public Task Add(Func<Task> action)
        => this.Add(async () => { await action(); return default(object); });

    public Task<T> Add<T>(Func<Task<T>> action)
    {
        var tcs = new TaskCompletionSource<T>();
        this.queue.Enqueue(() => CreateTaskAsync(action, tcs));
        this.Run();
        return tcs.Task;
    }

    public Task Interrupt(Action action)
        => this.Interrupt(() => { action(); return default(object); });

    public Task<T> Interrupt<T>(Func<T> action)
    {
        var tcs = new TaskCompletionSource<T>();
        Task.Run(() =>
        {
            var spin = new SpinWait();
            while (Interlocked.CompareExchange(ref this.immediate, () => CreateTask(action, tcs), null) != null)
                spin.SpinOnce();
            this.Run();
        });
        return tcs.Task;
    }

    public Task Interrupt(Func<Task> action)
        => this.Interrupt(async () => { await action(); return default(object); });

    public Task<T> Interrupt<T>(Func<Task<T>> action)
    {
        var tcs = new TaskCompletionSource<T>();
        Task.Run(() =>
        {
            var spin = new SpinWait();
            while (Interlocked.CompareExchange(ref this.immediate, async () => await CreateTaskAsync(action, tcs).ConfigureAwait(false), null) != null)
                spin.SpinOnce();
            this.Run();
        });
        return tcs.Task;
    }

    private Task CreateTask<T>(Func<T> action, TaskCompletionSource<T> tcs)
    {
        try
        {
            tcs.TrySetResult(action());
        }
        catch (Exception exception)
        {
            tcs.TrySetException(exception);
        }
        return Task.FromResult(default(object));
    }

    private async Task CreateTaskAsync<T>(Func<Task<T>> asyncAction, TaskCompletionSource<T> tcs)
    {
        try
        {
            tcs.TrySetResult(await asyncAction().ConfigureAwait(false));
        }
        catch (Exception exception)
        {
            tcs.TrySetException(exception);
        }
    }

    private void Run()
    {
        if (Interlocked.Exchange(ref this.isRunning, 1) != 0)
            return;
        Task.Run(async () =>
        {
            var action = default(Func<Task>);
            while (true)
            {
                if (this.immediate != null)
                    await Interlocked.Exchange(ref this.immediate, null).Invoke().ConfigureAwait(false);
                else if (this.queue.TryDequeue(out action))
                    await action().ConfigureAwait(false);
                else
                    break;
            }
            Interlocked.Exchange(ref this.isRunning, 0);
            if (this.queue.Count != 0)
                this.Run();
        });
    }
}

以下、少しだけ使い方を解説します。

private ActionQueueScheduler scheduler = new ActionQueueScheduler();

private async Task FolderSync1()
{
    while (true)
    {
        await this.scheduler.Add(async () =>
        {
            CCommon common = new CCommon();
            await common.RobocopyTask("(PC1のフォルダ1のパス)", "(PC2のフォルダ1のパス)", "ファイル1");
        }).ConfigureAwait(false); // queueの中に処理を入れてawaitで完了を待つ
        await Task.Delay(500).ConfigureAwait(false); // 500ms周期でファイルを同期する(PC1とPC2間)
    }
}


ファイル毎にループメソッドを作り、一つのスケジューラーにそれぞれ独立でAddしていくだけで良いです。
これだけの記述で、Syncの処理が「完了してから」500ms後に、再びSyncが実行されるようにスケジュールされます。
他のメソッドが増えたとしても、このメソッドをコピーしたものを作成し、それを同時に開始するだけで大丈夫です。
「同期処理の処理速度より、スケジューラーのタスク登録速度のほうが明らかに速い」という条件でさえ無ければ、問題無く動作するはずです。

AddメソッドとInterruptメソッドを提供しており、それぞれに同期メソッド、非同期メソッドを登録可能で、実行結果を受け取ることもできます。各メソッドは「登録したメソッドが実行完了したかどうかを表すTask(Task<T>)」を返すため、これをawaitすることで、実行結果を受け取ったり、スケジューリングから次回実行までの時間調整などが簡単に行えます。
上記の使用例であれば、同期処理が完了してから次の同期処理を再登録という流れになるので、「完了してないのに次の同期が始まる」ということが起こることもないため、安全です。
Interruptはキューを無視して処理を割り込み実行するためのメソッドなので使用には注意してください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/13 15:40

    tamotoさん ご回答理解しました。ありがとうございます。

    これに関連する部分で理解を深めたいので質問させて頂きますが、

    public Task<T> Add<T>(Func<CancellationToken, T> action)
    {
    var tcs = new TaskCompletionSource<T>();
    this.queue.Enqueue(() => this.InvokeAction(action, tcs));
    this.Run();
    return tcs.Task;
    }

    上記のAdd()の戻り値がtcs.Taskとなっています。
    これは引数actionで渡されるタスク処理が実行完了した状態を返すものでしょうか。
    となると、このAdd()内でactionが実行完了するまで待ってくれる?ひいては上述の「await task; // 完了していなければ完了するまで待つ」のawaitは必要なのかな?という疑問が沸いてしまいました。
    すみませんが、この部分について正しい解析をご教授頂けませんでしょうか。よろしくお願い致します。

    キャンセル

  • 2017/02/13 16:04 編集

    このスケジューラは一つのactionを動かすために3つ以上の独立した役割を持つTaskが作られたり動いたりしてるのでちょっと難解なのです。。
    tcs.Taskは、
    「actionが実際に処理完了したとき『完了』となる」
    「actionが何らかの例外を発生したとき『失敗』となる」
    「action内でOperationCanceledExceptionが投げられたとき『キャンセル』となる」
    という一つのタスクオブジェクトとなっています。
    TaskCompletionSource<T>のことは理解していますか?これを利用することで、(スケジューラが勝手に実行する)actionの実行状況をTaskという形に固めて、ユーザが容易に取り扱えるようにしたものです。
    Addが完了してTaskが返されるのは「actionをスケジューラに登録完了した」タイミングであり、スケジューラによってactionが実際に実行されたときに初めて『完了』状態となるため、これをawaitすることは大きな意味があります。
    tcsの扱い方は、InvokeActionのコード部分を読むと分かりやすいかもしれません。
    ---
    追記
    もしかして、this.Run()が気になっているでしょうか。これは、スケジューラにアイテムが追加された時、スケジューラのタスク処理機構を(非同期に)稼働させる命令を投げるものなので、実行後は即座に制御がAddに戻っています。

    キャンセル

  • 2017/02/28 14:16

    tamotoさん
    TaskCompletionSourceの動きについてご教授ありがとうございます。理解しました。
    また、this.Run()の動きは、おっしゃる部分は理解しております。ありがとうございます。

    本件についてはこちらで実装完了できましたので、ベストアンサーとさせて頂きます。
    3ヶ月の長きに渡りご教授頂きまして、誠にありがとうございました。

    キャンセル

+1

Q1に関しては、使い方の解説サイト等は多分既にご覧になっていると思われますのでそれ以上の解説は私にはできません。
強いて言えば、シンプルなテストプログラムを作って、実際にいろいろいじり倒してみるのが近道だと思います。

Q2に関しては、高度な誤魔化し技術もいろいろあるとは思いますが、まずは処理を高速化すべきだと思います。
fastcopyという有名な高速コピーのソフトウェアがあるので、それの使用を検討してみてください。
まぁそれでも根本的な解決ではないので、いつかは頭打ちになる可能性はありますが。

それ以外だと、やはり前提条件次第ですね。どうしてもどこかしら妥協すべきだと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/25 17:28

    ishi9さん ご回答ありがとうございます。

    Q1に関しては解説サイトを読み、プログラムも組んでいるのですが、awaitの入れ子になったときにどのように動くかいまいち頭の中で整理できておらず、わかっていらっしゃる方に回答頂きたくて質問させて頂きました。基本的な内容の質問ですが、もし可能であればお手数ですがご回答頂けると大変助かります。

    Q2に関してはfastcopyのご紹介ありがとうございます。
    フリーなソフトについてはrobocopy以外のものは調べてみました。
    ただ開発の前提としてmicrosoft純正のコードを使用したいこだわり(microsoftが動作を保証しているため)があり、robocopyにしております。基本的にはrobocopyとfastcopyでそこまで大きく速度が変わることはないのかなと、いくつかの比較サイトを見て感じております。
    前提条件はおっしゃるように妥協する必要があると感じております。
    ただ、現状の遅延があまりにひどいもので、そこを多少なりとも改善できる方法があればよいかと思い質問させて頂きました。

    キャンセル

  • 2016/11/25 17:41

    後はまぁ小さいファイルが大量にあるケースの場合、
    一度全部圧縮してから転送して、転送先で解凍した方が速いケースもあります。

    キャンセル

  • 2016/11/28 18:43

    ishi9さん ご回答ありがとうございます。

    おっしゃるアイデアを試してみたいと思います。
    教えて頂きありがとうございました。

    キャンセル

+1

あんまり質問読んでないで回答するのですが(ごめんなさい...)、非同期と並列処理を混同していますでしょうか。

非同期処理は、同期処理だとロックされるスレッドを、切り離す(=コンテキストスイッチを発生させなくする)だけで、並列処理をするわけではありません。
並列処理は...並列に処理するだけです。(非同期にしたスレッドを並列で処理するって感じです。)

コンテキストスイッチは、CPUにとって中々コストがかかるので、例えばIOバウンドのときは、CPUを使ってないのにコンテキストスイッチさせてCPUを無駄に使うのが無駄=非同期処理にすべき。
逆にCPUバウンドだと、非同期してもそもそもCPU使ってるので無駄(なケースが考えられる)とかです。
つまり、今回ファイルのIO待ちの時は、並列処理をしなくても非同期処理をする価値があるのです。

async/awaitは、非同期処理です。

(ちょっと極端な例かもですが、)asyncをして、すぐ下の行でawaitすればただの非同期処理です。記載されたコードにもありますが、Taskを複数走らせて、whenAllなどで待ってあげたりすることで並列処理をすることができます。

という点がクリアになれば、async/awaitすべきところや、並列がどう動くかについて解決できるかと思いまして、書いてみました。
(ちなみに、並列処理でもスレッドウィンドウを使ってスレッドを追いながらデバッグができます...)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/28 18:35

    BEACHSIDEさん ご回答ありがとうございます。

    非同期と並列について混同があり、気づかさせて頂きました。
    また、CPUバウンド,IOバウンドに関しても学ばさせて頂きました。
    解決に向けてヒントを頂けましたので、また調べて考えてみます。
    ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.59%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る