C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

パラレル処理での共通変数

[トピック内 28 記事 (1 - 20 表示)]  << 0 | 1 >>

■97577 / inTopicNo.1)  パラレル処理での共通変数
  
□投稿者/ メタルスライム (1回)-(2021/06/08(Tue) 03:53:56)

分類:[.NET 全般] 

お世話になります

パラレルでのインターフェイスの実装について教えて下さい


formにButton1がある状態です


’●ユーザーインターフェイスに進行状況を表示したい

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Parallel.For(0, 50,
Sub(i)
System.Threading.Thread.Sleep(100)
Me.Text = i.ToString
End Sub)
MsgBox(”完了”)
End Sub



’●共通の変数に値を代入したい

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

Dim L As New List(Of String)
Parallel.For(0, 50,
Sub(i)
System.Threading.Thread.Sleep(100)
L.Add(i.ToString)
End Sub)
MsgBox(”完了” & L.Count)

End Sub



上記コードはうまくいったりもしますが
だめなことはわかりますい

これらを実現するための簡単な方法はありますか?



引用返信 編集キー/
■97578 / inTopicNo.2)  Re[1]: パラレル処理での共通変数
□投稿者/ WebSurfer (2255回)-(2021/06/08(Tue) 07:09:18)
No97577 (メタルスライム さん) に返信
> お世話になります
>
> パラレルでのインターフェイスの実装について教えて下さい
>
>
> formにButton1がある状態です

作っているのは WinForms ですか?

開発環境を書いてください。OS, Visual Studio のバージョン, .NET Frsmework なのか Core なのかとそのバージョンなど。

引用返信 編集キー/
■97579 / inTopicNo.3)  Re[1]: パラレル処理での共通変数
□投稿者/ WebSurfer (2256回)-(2021/06/08(Tue) 08:22:54)
No97577 (メタルスライム さん) に返信

それから、WinForms + タスク並列ライブラリですと処理が終わるまで UI はブロック
される (フリーズしたようになる) と思いますが、それはいいのですか?
引用返信 編集キー/
■97585 / inTopicNo.4)  Re[1]: パラレル処理での共通変数
□投稿者/ WebSurfer (2259回)-(2021/06/08(Tue) 14:38:36)
No97577 (メタルスライム さん) に返信

UI スレッドで呼び出して進捗を表示するには Progress クラスが利用できます。詳しくは以下の記事を
見てください。

WPF/Windowsフォーム:時間のかかる処理をバックグラウンドで実行するには?
https://www.atmarkit.co.jp/ait/articles/1512/02/news019.html

それを使って試してみました。具体的には以下の記事のサンプルに進捗表示用の toolStripProgressBar 
と toolStripStatusLabel を追加し、

タスク並列ライブラリ (TPL)
http://surferonwww.info/BlogEngine/post/2020/12/27/task-parallel-library.aspx

さらに下のコードを追加して動かしてみました。

using System;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;

namespace WinFormsApp1
{
    public partial class TaskParallelLibrary : Form
    {
        public TaskParallelLibrary()
        {
            InitializeComponent();

            toolStripStatusLabel1.Text = "";
            toolStripProgressBar1.Value = 0;
        }

        // ・・・中略・・・

        // ***** 進捗を表示 ****

        int currentProgress = 0;       
        

        // 進捗をプログレスバーとラベルに表示するコールバック
        // UIスレッドで呼び出される
        private void ShowProgress(int percent)
        {
            currentProgress += percent;
            toolStripStatusLabel1.Text = currentProgress + "%完了";
            toolStripProgressBar1.Value = currentProgress;
        }

        private void WorkProgress()
        {
            Thread.Sleep(500);
            this.Invoke((Action)(() =>
            {
                currentProgress++;
                toolStripStatusLabel1.Text = currentProgress + "%完了";
                toolStripProgressBar1.Value = currentProgress;
            }));
        }

        private void WorkProgress2(IProgress<int> progress)
        {
            Thread.Sleep(500);
            progress.Report(1);
        }

        private void TplProgress_Click(object sender, EventArgs e)
        {
            currentProgress = 0;
            toolStripStatusLabel1.Text = "";
            toolStripProgressBar1.Value = 0;

            // Application.DoEvents で上の設定を即ラベルに反映
            Application.DoEvents();

            //Parallel.For(0, 100, _ => WorkProgress());

            var p = new Progress<int>(ShowProgress);
            Parallel.For(0, 100, _ => WorkProgress2(p));
        }

        private async void WhenAllProgress_Click(object sender, EventArgs e)
        {
            currentProgress = 0;
            toolStripStatusLabel1.Text = "";
            toolStripProgressBar1.Value = 0;

            var p = new Progress<int>(ShowProgress);
            var taskList = new List<Task>();
            for (int i = 0; i < 100; i++)
            {
                taskList.Add(Task.Run(() => WorkProgress2(p)));
            }

            // WaitAll は await で待機できないので注意
            await Task.WhenAll(taskList.ToArray());
        }
    }
}

紹介した記事に書いた通り、WinForms でタスク並列ライブラリを使う場合、

(1) 並列に実行される複数のメソッドがすべて完了するまで UI がブロックされる。
(2) 並列実行するメソッドには非同期版は使えない。

という問題がありますが、さらに上記のコード TplProgress_Click の Parallel.For を使っている方は、
Progress クラスを利用しても並列に実行される複数のメソッドがすべて完了するまで UI には反映されな
いという問題がありました。

Progress クラスではなくて、Form.Invoke を使う WorkProgress() メドッソの方はなぜか途中でハング
してしまいます。理由は気力が無くて調べ切れてません。

WhenAllProgress_Click の、タスク並列ライブラリは使わないで、await Task.WhenAll(...) で待機する
方は期待通り進捗が表示されます。

どなたか、Parallel.For を使ってできないかチャレンジして、うまくいったら結果を書いてくれると幸
いです。

引用返信 編集キー/
■97587 / inTopicNo.5)  Re[2]: パラレル処理での共通変数
□投稿者/ メタルスライム (2回)-(2021/06/09(Wed) 06:21:12)
No97585 (WebSurfer さん) に返信


ご教授ありがとうございます。

当該プロジェクトは .NET 4.0 でつくています
開発環境はVS2017コミュニティ OSはWIN10 .NET Framework 

Windousformのソフトウェアです


今、教えていただいたホームページは少し前、試してみたことがあることにきづきました
なぜかエラーが消えないと思ったら、残念ながらバージョン4.5が必要でした。
これを期に.NETを4.0から4.6にあげたほうが良いのでしょうね。

と思って、
ホームページを見させていただいたのですが
結局のところ Parallel.For ではできない、が結論ということなのでしょうか




また共通の変数に値を代入したい件だけ実現しようと思えば
下記のコードの考え方は正しい方法なのでしょうか
SyncLock L ' 排他制御を書いてみました。
実行すればこちらの環境では約3秒で終わるので17倍で並行しているようです


Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

Dim L As New List(Of String)
Parallel.For(0, 50,
Sub(i)
System.Threading.Thread.Sleep(1000)

SyncLock L ' 排他制御
L.Add(i.ToString)
End SyncLock

End Sub)
MsgBox(”完了” & L.Count)

End Sub




引用返信 編集キー/
■97588 / inTopicNo.6)  Re[3]: パラレル処理での共通変数
□投稿者/ 魔界の仮面弁士 (3124回)-(2021/06/09(Wed) 10:33:10)
No97587 (メタルスライム さん) に返信
> 当該プロジェクトは .NET 4.0 でつくています

現時点で、新規開発のために .NET Framework 4 を採用されることはお奨めしません。

現行バージョンの OS に .NET 4.0 をインストールすることはできませんし
.NET 4.0 は 2016/01/12 にサポート期限が終了しているためです。
(現在でもサポートされているバージョンは 4.5.2 以上および 3.5SP1 のみ)


> これを期に.NETを4.0から4.6にあげたほうが良いのでしょうね。
4.6 がリリースされたのは 6 年前ですし、今となってはそれも古い部類ですね。

サーバー系 OS や Win10 LTSB/LTSC などのために、あえて古いバージョンを採用することもありますが、
現行バージョンの Windows 10 であれば、既定で 4.8 がインストールされた状態になっているはずです。

Visual Studio 2017 は 4.6.1 世代の開発環境ですが、Developer Pack の導入により、
それ以降のバージョンの .NET Framework 4.8 向けの開発にも利用できます。
https://dotnet.microsoft.com/download/dotnet-framework
https://dot.net/


参考までに、対応する OS バージョン一覧を載せておきます。

.NET 3.5 … Win7/2008/2008R2 にプリインストール(それ以降の OS にも導入可能)
.NET 4.0 … プリインストールされた OS は無い(XP〜Vista,2003〜2008R2に導入可能)
.NET 4.5 … Win8/2012 にプリインストール(Vista/7,2008/2008R2に導入可能)
.NET 4.5.1… Win8.1/2012R2 にプリインストール(Vista〜8,2008〜2012に導入可能)
.NET 4.5.2… プリインストールされた OS は無い(Vista〜8.1,2008〜2012R2に導入可能)
.NET 4.6 … Win10[1507] にプリインストール(Vista〜8.1,2008〜2012R2に導入可能)
.NET 4.6.1… Win10[1511] にプリインストール(7〜8.1,10[1507],2008R2〜2012R2に導入可能)
.NET 4.6.2… Win10[1607]/2016 にプリインストール(7,8.1,10[1507〜1511],2008R2〜2012R2に導入可能)
.NET 4.7 … Win10[1703] にプリインストール(7,8.1,10[1607],2008R2〜2016に導入可能)
.NET 4.7.1… Win10[1709] にプリインストール(7,8.1,10[1607〜1703],2008R2〜2016に導入可能)
.NET 4.7.2… Win10[1803〜1809]/2019 にプリインストール(7,8.1,10[1607〜1709],2008R2〜2016に導入可能)
.NET 4.8 … Win10[1903以降] にプリインストール(7,8.1,10[1607〜1809],2008R2〜2019に導入可能)

※ .NET 5.0 や .NET 6.0 Preview は、.NET Framework とは系図が異なるので省略。



> 結局のところ Parallel.For ではできない、が結論ということなのでしょうか

完了するまで長い時間がかかる処理を、UI スレッドから Parallel.For してはいけません。

Parallel は、「短い時間で終わる作業」が多数ある場合に、
それを、指定した数のスレッドで分担して処理させる機能です。

また、WebSurfer さんが示されているように、Parallel.For や .ForEach を呼び出した場合、
その呼び出し元は、作業完了まで同期的に待たされることになります。
これらの呼び出しは非同期処理ではありません。


画面(UI)をフリーズさせること無く、長時間にわたるパラレル処理を行うのであれば、
バックグラウンドスレッドで Parallel クラスを呼び出してみてください。

.NET Framework 4.5 以降であれば Async/Await を使う所ですが、
.NET Framework 2.0〜4.0 世代で非同期な作業をさせたいのであれば、
Form に BackgroundWorker を貼って使うのがお手軽かと思います。



> また共通の変数に値を代入したい件だけ実現しようと思えば
> 下記のコードの考え方は正しい方法なのでしょうか

Parallel.For は並列処理ですが非同期処理ではありませんので、
サンプルとはいえ Sleep(1000) を呼ぶようなコードは本来望ましくありません。

また、SyncLock は比較的実行コストの高い操作となります。

自作クラスを用意するほどのものでもない軽量なコレクションの場合には、
SyncLock ではなく、スレッドセーフなコレクションやメソッドを使う方が望ましいです。
https://www.atmarkit.co.jp/ait/articles/1802/07/news019.html
https://www.atmarkit.co.jp/ait/articles/0505/25/news113_3.html

SyncLock ステーメントを使うとしても、フォームから直接呼び出すのは事故の元です。
スレッドセーフではない読み書き処理のすべての箇所をロックせねばならないため、
うっかり SyncLock を書き忘れて変数を読み書きしてしまう事故が発生しやすいですし、
使いどころを間違えるとデッドロックの要因にもなりえるためです。

そのため SyncLock ステーメントを使うのであれば、呼び出し漏れがないように
読み書きする処理をプロパティやメソッドに入れて内部的に SyncLock し、
かつ、そのロックオブジェクトを「外部から操作できない Private なオブジェクト」を
指定することで、ロック漏れやデッドロックを防止します。
https://www.itmedia.co.jp/enterprise/articles/0503/23/news086_2.html
引用返信 編集キー/
■97589 / inTopicNo.7)  Re[3]: パラレル処理での共通変数
□投稿者/ WebSurfer (2260回)-(2021/06/09(Wed) 11:08:31)
No97587 (メタルスライム さん) に返信

> 当該プロジェクトは .NET 4.0 でつくています
> これを期に.NETを4.0から4.6にあげたほうが良いのでしょうね。

.NET Framework 4 のサポートは当の昔に終了しています。4.6.1 より前のバージョンも来年サポート終了
とのことで、この際 4.6.2 以降にアップグレードした方が良さそうです。

ライフサイクルに関する FAQ - .NET Framework
https://docs.microsoft.com/ja-jp/lifecycle/faq/dotnet-framework


> 結局のところ Parallel.For ではできない、が結論ということなのでしょうか

「結論」と言えるほどの自信はないですが、自分が試した限りではタスク並列ライブラリを使った場合は
進捗表示はできなかったです。

上のレスで書いた「(1) 並列に実行される複数のメソッドがすべて完了するまで UI がブロックされる。」
というところが何ともならなかったです。


> また共通の変数に値を代入したい件だけ実現しようと思えば下記のコードの考え方は正しい方法なのでしょうか

不正なクロススレッドコールになってなければ良さそうです。

TextBox.Text に書き込んだりすると「不正なクロススレッドコール」になって間違いなく例外がスローさ
れると思いますが、List<string> に Add するのはどうなるか分かりません。

CheckForIllegalCrossThreadCalls プロパティを true(デフォルトは false)に設定するとか、Visual
Studio からデバッグ実行すると確認できるかもしれませんので試してみてはいかがですか。

詳しくは下記の記事を見てください。

不正なクロススレッドコールの捕捉
http://surferonwww.info/BlogEngine/post/2019/07/11/detect-illegal-cross-thread-calls.aspx
引用返信 編集キー/
■97594 / inTopicNo.8)  Re[4]: パラレル処理での共通変数
□投稿者/ test (1回)-(2021/06/10(Thu) 23:16:34)
test

No97589 (WebSurfer さん) に返信
> ■No97587 (メタルスライム さん) に返信
>
>>当該プロジェクトは .NET 4.0 でつくています
>>これを期に.NETを4.0から4.6にあげたほうが良いのでしょうね。
>
> .NET Framework 4 のサポートは当の昔に終了しています。4.6.1 より前のバージョンも来年サポート終了
> とのことで、この際 4.6.2 以降にアップグレードした方が良さそうです。
>
> ライフサイクルに関する FAQ - .NET Framework
> https://docs.microsoft.com/ja-jp/lifecycle/faq/dotnet-framework
>
>
>>結局のところ Parallel.For ではできない、が結論ということなのでしょうか
>
> 「結論」と言えるほどの自信はないですが、自分が試した限りではタスク並列ライブラリを使った場合は
> 進捗表示はできなかったです。
>
> 上のレスで書いた「(1) 並列に実行される複数のメソッドがすべて完了するまで UI がブロックされる。」
> というところが何ともならなかったです。
>
>
>>また共通の変数に値を代入したい件だけ実現しようと思えば下記のコードの考え方は正しい方法なのでしょうか
>
> 不正なクロススレッドコールになってなければ良さそうです。
>
> TextBox.Text に書き込んだりすると「不正なクロススレッドコール」になって間違いなく例外がスローさ
> れると思いますが、List<string> に Add するのはどうなるか分かりません。
>
> CheckForIllegalCrossThreadCalls プロパティを true(デフォルトは false)に設定するとか、Visual
> Studio からデバッグ実行すると確認できるかもしれませんので試してみてはいかがですか。
>
> 詳しくは下記の記事を見てください。
>
> 不正なクロススレッドコールの捕捉
> http://surferonwww.info/BlogEngine/post/2019/07/11/detect-illegal-cross-thread-calls.aspx
引用返信 編集キー/
■97595 / inTopicNo.9)  Re[5]: パラレル処理での共通変数
□投稿者/ test (2回)-(2021/06/10(Thu) 23:19:05)
No97594 (test さん) に返信
> test
>
> ■No97589 (WebSurfer さん) に返信
>>■No97587 (メタルスライム さん) に返信
>>
> >>当該プロジェクトは .NET 4.0 でつくています
> >>これを期に.NETを4.0から4.6にあげたほうが良いのでしょうね。
>>
>>.NET Framework 4 のサポートは当の昔に終了しています。4.6.1 より前のバージョンも来年サポート終了
>>とのことで、この際 4.6.2 以降にアップグレードした方が良さそうです。
>>
>>ライフサイクルに関する FAQ - .NET Framework
>>https://docs.microsoft.com/ja-jp/lifecycle/faq/dotnet-framework
>>
>>
> >>結局のところ Parallel.For ではできない、が結論ということなのでしょうか
>>
>>「結論」と言えるほどの自信はないですが、自分が試した限りではタスク並列ライブラリを使った場合は
>>進捗表示はできなかったです。
>>
>>上のレスで書いた「(1) 並列に実行される複数のメソッドがすべて完了するまで UI がブロックされる。」
>>というところが何ともならなかったです。
>>
>>
> >>また共通の変数に値を代入したい件だけ実現しようと思えば下記のコードの考え方は正しい方法なのでしょうか
>>
>>不正なクロススレッドコールになってなければ良さそうです。
>>
>>TextBox.Text に書き込んだりすると「不正なクロススレッドコール」になって間違いなく例外がスローさ
>>れると思いますが、List<string> に Add するのはどうなるか分かりません。
>>
>>CheckForIllegalCrossThreadCalls プロパティを true(デフォルトは false)に設定するとか、Visual
>>Studio からデバッグ実行すると確認できるかもしれませんので試してみてはいかがですか。
>>
>>詳しくは下記の記事を見てください。
>>
>>不正なクロススレッドコールの捕捉
>>http://surferonwww.info/BlogEngine/post/2019/07/11/detect-illegal-cross-thread-calls.aspx
引用返信 編集キー/
■97604 / inTopicNo.10)  Re[6]: パラレル処理での共通変数
□投稿者/ メタルスライム (3回)-(2021/06/12(Sat) 13:44:35)
魔界の仮面弁士 さま

ご教授ありがとうございます
バージョンのわかりやすい説明ありがとうございます

XP対象 4.0 VISTA対象 4.6 WIN7以降対象 4.8 ということですね

ちなみに「同期的な処理を短縮したい」で
非同期処理を望んでるだけではありません(怖すぎて)

【やりたいこと】
●同期的なコードの流れの中で一部の繰り返し処理をパラレルを使い時短したい
●時短している状況をプログレスバーに表示したい


syncLockは実行コストが高いとのことですが

実行コストが高いこと以外のデメリットがないなら、わかりやすくて簡単で良いのですが
体感できない程度の場合でも、一般的に推奨されないものなのでしょうか?





WebSurfer 様

貴重な体験談ありがとうございます

やはりマルチスレッドは恐ろしいと思ってしまいました。
.NETを4.6にあげて、下記の await Task.WhenAll をやってみたいと思います
これもマルチスレッドなのかもしれませんが・・・

>WhenAllProgress_Click の、タスク並列ライブラリは使わないで、await Task.WhenAll(...) で待機する
>方は期待通り進捗が表示されます。




引用返信 編集キー/
■97605 / inTopicNo.11)  Re[7]: パラレル処理での共通変数
□投稿者/ WebSurfer (2263回)-(2021/06/12(Sat) 16:59:27)
No97604 (メタルスライム さん) に返信

> .NETを4.6にあげて、下記の await Task.WhenAll をやってみたいと思います

プログレスを表示したいならそれしかないと思います。試してみてはいかがですか? そのうえで
どうするか決めたらよいと思います。

ただし、UI スレッドをブロックされるのは構わない(その場合プログレスの表示は諦めることに
なりますが)、並列で実行されるメソッドの実行結果を配列で取得できれば良いのであれば、紹介
した記事(URL 下記に再掲)のようにすればロックを使わなくても済むと思いますけど。

タスク並列ライブラリ (TPL)
http://surferonwww.info/BlogEngine/post/2020/12/27/task-parallel-library.aspx

> これもマルチスレッドなのかもしれませんが・・・

TPL も await Task.WhenAll もマルチスレッド(複数のスレッドで実行される)です。前者は UI
スレッドをブロックする、後者はブロックしないという違いはありますが。
引用返信 編集キー/
■97607 / inTopicNo.12)  Re[7]: パラレル処理での共通変数
□投稿者/ 魔界の仮面弁士 (3128回)-(2021/06/12(Sat) 18:55:20)
No97604 (メタルスライム さん) に返信
> 【やりたいこと】
> ●同期的なコードの流れの中で一部の繰り返し処理をパラレルを使い時短したい

UI スレッドからの動作なら、まずは「Task」や「BackgruondWorker」を使うことから始めましょう。
Parallel については、それとはまた別の話。

UI スレッドを待たせてしまうほどの処理を、Parallel だけで短くできるかという微妙です。
Parallel を使う上で「やってはいけないこと」を紹介しておきますね。
https://docs.microsoft.com/ja-jp/dotnet/standard/parallel-programming/potential-pitfalls-with-plinq#dont-assume-that-parallel-is-always-faster


> ●時短している状況をプログレスバーに表示したい

Task + Parallel ベースなら TaskScheduler.FromCurrentSynchronizationContext とか。
https://docs.microsoft.com/ja-jp/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext
http://www.kanazawa-net.ne.jp/~pmansato/parallel/parallel_ui.htm

まぁ、色々なやり方があるとは思います。たとえばこんな実装とか。
https://qiita.com/longlongago_k/items/8f19d84fce6dd677922e


個々の処理結果をスレッドセーフなコレクションで受け取りたいなら、
System.Collections.Concurrent 名前空間のクラスを使えます。前回紹介した BlockingCollection もこれ。


通知が一斉に通知されるタイミングと殆ど通知されないタイミングがあるようなケースなど、
通知数があまりに頻繁過ぎる場合には、窓口となる UI スレッドでの受領が渋滞して、
ボトルネックになるケースもあります。

その場合は、同期的な通知にせず、結果をスレッドセーフなオブジェクトで受け渡し、
UI 側は「他の作業が行われていないアイドル時」のみに
進捗表示を書き換えるような処理にすると良いかも知れません。
たとえば、Application の Idle イベントや、Timer の Tick などのタイミングで。


プログレスバーのみの話なら、初めに上限値ありきの話となるわけなので、
たとえば事前に総数が分かっている配列を Parallel.For していき、
「処理済みの数」だけをカウントしてプログレスバーに表示するようなケースが思い当たります。
この場合はたとえば
 Private 処理済みの数 As Integer = 0
なフィールド変数でも用意しておき、各パラレル側では
Interlocked.Increment を使って値を書き換える…ということもできそうです。

処理数では無く、ファイルサイズの合計値などを求めるケースであれば、
Interlocked.Increment を Interlocked.Add メソッドに変更します。
状況によっては、読み取り時に Interlocked.Read が必要になることがある点に注意。



> syncLockは実行コストが高いとのことですが
> 実行コストが高いこと以外のデメリットがないなら、わかりやすくて簡単で良いのですが
> 体感できない程度の場合でも、一般的に推奨されないものなのでしょうか?

さほど細かい制御が必要でない単純な排他制御なら、SyncLock で十分だと思います。
SyncLock が悪いわけでは無く、状況によっては最適解ではない、という話です。

単一変数の読み書きを排他制御するだけなら、そもそも SyncLock は使わず、
Interlocked クラスやスレッドセーフ コレクションを使った方が効率が良いでしょう。

複数の変数を同時に排他制御するような場合は、SyncLock ステートメントを使ったりもしますが、
同じ変数を複数のスレッドから参照しているようなケースだと、同時読み込みができなくなります。
このような場合、代わりに ReaderWriterLock クラスを使えば、
「書き換えている最中でなければ、複数のスレッドが同時に読み取れる」わけです。

また、SyncLock ステートメントは、内部的には Monitor.Enter/.Exit に相当しますが、
Monitor.TryEnter メソッドに相当する機能は無いため、アクセスの競合が発生した場合の
タイムアウトが必要な場合は、別の排他制御機構が必要になりますね。
引用返信 編集キー/
■97608 / inTopicNo.13)  Re[7]: パラレル処理での共通変数
□投稿者/ WebSurfer (2264回)-(2021/06/13(Sun) 11:31:35)
No97604 (メタルスライム さん) に返信

今更ながらですが、async / await が使えれば TPL を使っても UI スレッドのブロックは回避できる方法に気が付き
ましたので以下に書いておきます。

具体的には No97585 の TplProgress_Click メソッドを以下のようにします。お試しください。

private async void TplProgress_Click(object sender, EventArgs e)
{
currentProgress = 0;
toolStripStatusLabel1.Text = "";
toolStripProgressBar1.Value = 0;

var p = new Progress<int>(ShowProgress);
await Task.Run(() => Parallel.For(0, 100, _ => WorkProgress2(p)));
}

No97585 の TplProgress_Click メソッドでは何故ダメだったかというと、UI スレッドがブロックされるため、

// 進捗をプログレスバーとラベルに表示するコールバック
// UIスレッドで呼び出される
private void ShowProgress(int percent)
{
currentProgress += percent;
toolStripStatusLabel1.Text = currentProgress + "%完了";
toolStripProgressBar1.Value = currentProgress;
}

はキューに溜るだけで TPL の並列処理が終わってからでないと実行されないからです。

TPL の並列処理が終わると UI スレッドのブロックが解除され、キューに溜った全てがメッセージループ一気に
実行されますが、一瞬で 100% になってしまいます。

async / await を使って TPL の部分を await Task.Run で動かすと UI スレッドはブロックされず、メッセー
ジループでマウスのクリックやキーボードのストロークなどのユーザーイベントが処理されることが分かりま
した。ShowProgress メソッドも UI スレッドで実行され、進捗は表示されました。

ただ、async / await が使えるなら、 No97585 のコード例の await Task.WhenAll で待機することもできます
ので、TPL は使わなくても良いのではないかという気はします。

Microsoft のドキュメント "TPL は、使用可能なすべてのプロセッサを最も効率的に使用するように、コンカ
レンシーの程度を動的に拡大します" と書いたありましたが、そこのところに意味があるのかもしれませんが。

なお、Parallel LINQ (PLINQ) の方は async / await を使っても UI スレッドがブロックされるのは回避でき
ないようです。

回避できない理由や対応策は分かりません。今はそこまで調べる気力がないので、今後の検討課題ということで。
引用返信 編集キー/
■97611 / inTopicNo.14)  Re[8]: パラレル処理での共通変数
□投稿者/ メタルスライム (4回)-(2021/06/13(Sun) 22:45:14)
魔界の仮面弁士さま

>UI スレッドからの動作なら、まずは「Task」や「BackgruondWorker」を使うことから始めましょう。
>Parallel については、それとはまた別の話。

とはいえ、Parallel で、これまでの同期的な動作(ボトルネックはLAN上のファイル操作)で
壊滅的に遅かった動作が何倍にもなりました。

極めてシンプルな操作で、UIに期待しなければ非常に簡易に導入できます。
例えば ローカルネットワーク上の共有ファイルのコピー を多数する場合
TASK や BackgruondWorker で同じ結果を得ることはできるのでしょうか



> WebSurferさま

ご教授ありがとうございます

記載のコード、是非試させていただきます



以上よろしくおねがいします







引用返信 編集キー/
■97614 / inTopicNo.15)  Re[9]: パラレル処理での共通変数
□投稿者/ 魔界の仮面弁士 (3129回)-(2021/06/14(Mon) 12:01:37)
No97611 (メタルスライム さん) に返信
> ボトルネックはLAN上のファイル操作
LAN上のファイル操作がボトルネックの要因だったため、Parallel を使ってみたけれど、
通知の仕方が分からない、という話なのでしょうか。
(ここまでのやり取りで、そうした前提条件は出ていなかったという認識)

UI はメッセージループによって動いており、基本的にシングルスレッドが大前提ですよね。

画面上に進捗表示したいのであれば、進捗表示は UI スレッドの仕事ですが、
先のコードだと、 UI スレッドが Parallel 完了まで待機状態になってしまい、
パラレル内の処理を画面に表示させることができません。
故に、 No97577 のような実装の仕方では通知できません。

それゆえ先の回答では、Task + Parallel の例を紹介させていただきました。

「完了まで秒単位以上の時間がかかる処理」を画面から呼び出す場合、
時間のかかる作業を別スレッドに担当させ、それを「非同期」で呼び出せるようにします。
(処理を「並列」化して、トータル時間が十分に短くなるようにするという選択肢もアリ)

この「時間がかかる場合」のパフォーマンスの方針として、このような資料があります。
WinForms ではなく UWP の物ですが、理想時間と許容最大時間の表は役に立つかと。
https://docs.microsoft.com/en-us/windows/uwp/debug-test-perf/planning-and-measuring-performance


> (ボトルネックはLAN上のファイル操作)で
> 壊滅的に遅かった動作が何倍にもなりました。
データの並列化なのか処理の並列化なのか分かりませんが、
たとえば LAN 上のファイル検索だとしたら、サーバーや物理ドライブごとに
分配されたような場合、十分な高速化が見込めるかと思います。
ボトルネックがストレージ性能やネットワーク負荷は別として。

ファイル操作用の Async バージョンの非同期メソッドも用意されていますが、
.NET 4.5 以降が対象ですね。(非同期の話であって並列化の話ではありません)
https://docs.microsoft.com/ja-jp/dotnet/standard/io/asynchronous-file-i-o

並列処理で完了通知やパイプライン処理が必要なら、TPL Dataflow というものもあります。
(System.Threading.Tasks.Dataflow 名前空間)
.NET Core には標準実装で、.NET Framewrok だと要 nuget です。
こちらも「.NET Framework 4.5 以上が必要」らしいので、今回は使えなさそうですが…。
https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.tasks.dataflow
https://www.nuget.org/packages/System.Threading.Tasks.Dataflow
https://qiita.com/ea54595/items/5383c51f94a4ffc17a46


> 極めてシンプルな操作で、UIに期待しなければ非常に簡易に導入できます。
状況や目的が分からないので、概要的な話が多くなってしまいました。

各パラレルが、一つの処理内で何度も進捗を通知するのか、
各パラレルが処理を終えることごとに進捗を通知したいのかも、
No97577 からの質問からでは読み解けなかったので…。

やりたいことが変われば、そのための処理も当然変わってくると思います。
たとえば動画再生の処理イメージで考えてみましょう。

生放送配信の動画を再生するにあたり、通信量が再生速度に追いつかない場合、
 (a) データを間引いて、途中のコマを飛ばして再生
 (b) 再生を一時的に止めたり遅くしたりして、データが届くまで待つ
 (c) あらかじめ十分なデータを受信しておいてから、タイムシフト再生
といった対策を取れますよね。(あくまで概念レベルの話ですが)

c については、それだけで足りなければ c+a や c+b となるかと思いますが、
a は「処理が優先」「通知は間引かれても良い」ケースで、
b は「通知が優先」「処理はその分遅くなっても良い」ケースであり、
それぞれの目的に応じて、手続きや手段も変わってくるかと思います。


> TASK や BackgruondWorker で同じ結果を得ることはできるのでしょうか

そもそもの目的が違うので、直接比較するものでは無いと思いますよ。
ボトルネック部分が直列性にあることなら、並列処理化することはもちろん有効ですが、
『並列(parallel)・直列(serial)』と『同期(sync)・非同期(async)』は別の話。


Parallel は、複数の処理を Partitioner によって分配してくれますが、
あくまで並列なだけであって、全体としては非同期ではありません。
ゆえに完了まで秒単位以上の時間がかかる場合、UI スレッドから単体で呼ぶのは望ましくありません。

BackgruondWorker は非同期処理のための仕組みであり、通知のための仕組みも持っています。
(通知イベントによって行われ、呼び出し元スレッドに対して同期的に発生します)
しかし、これ単体では複数のスレッドを管理することができません。

Task はパイプライン処理などにも使えますし、TaskScheduler および Async/Await が
あることで、同期的な呼び出しや非同期的な呼び出しにも使えます。
しかし、複数の処理をタスクに分配するといった管理は自前で行う必要があるでしょう。

たとえば Task.WaitAll を Parallel.Invoke の代わりに使えることがありますが、
Parallel は Action(Of ) のみです。Task は Action(Of ) / Func(Of ) ですね。
引用返信 編集キー/
■97617 / inTopicNo.16)  Re[10]: パラレル処理での共通変数
□投稿者/ 魔界の仮面弁士 (3130回)-(2021/06/14(Mon) 13:22:50)
2021/06/14(Mon) 13:31:10 編集(投稿者)

No97614 (魔界の仮面弁士) に追記
> それゆえ先の回答では、Task + Parallel の例を紹介させていただきました。

No97588 の繰り返しになりますが、Parallel のすべての処理が終わるまで
UI 要素は待機状態になり、画面が更新されません。

Parallel (すなわち並列) を使おうと使うまいと、長い処理がある場合は、
それを別のスレッドに「作業依頼」するだけにして、処理の完了を持たずに
UI スレッドに戻ってくるようにします。これが「非同期」処理。

もちろん、Task 内から Parallel を呼んでも構いません。


で、肝心の通知ですが…ごめんなさい。先の回答において、

★[データとタスクの並列化における注意点]
 https://docs.microsoft.com/ja-jp/dotnet/standard/parallel-programming/potential-pitfalls-in-data-and-task-parallelism

というリンクを貼ったつもりでしたが、改めて読み返してみると、
No97607 では<URL の似た無関係のリンク>を貼ってしまっていました。
>> https://docs.microsoft.com/ja-jp/dotnet/standard/parallel-programming/potential-pitfalls-with-plinq#dont-assume-that-parallel-is-always-faster

上記 ★ の注意点には、誤った例(Button1_Click)と
その改修案(Button2_Click)が書かれているので掲載しておきます。
※Button2_Click が何故か Handles Button1.Click と書かれているので、試す場合は注意。

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
  Dim iterations As Integer = 20
  Parallel.For(0, iterations, Sub(x)
                  Button1.Invoke(Sub()
                            DisplayProgress(x)
                          End Sub)
                End Sub)
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

  Dim iterations As Integer = 20
  Task.Factory.StartNew(Sub() Parallel.For(0, iterations, Sub(x)
                                Button1.Invoke(Sub()
                                          DisplayProgress(x)
                                        End Sub)
                              End Sub))
End Sub


最初の質問 No97577 の前半部に似せるのであれば、
iterations を 20→50に増やし、
「Sub(x)」と「Button1.Invoke(Sub()」の間に
System.Threading.Thread.Sleep(100) を置けば良いでしょう。
DisplayProgress は ListBox1.Items.Add あたりに置き換えると分かりやすいかも。

最初の質問 No97577 の後半部(共通の変数)は説明済みなので省略。


ついでに、Task.Factory.StartNew と Task.Run の違いについても貼っておきます。
http://outside6.wp.xdomain.jp/2016/08/04/post-205/
引用返信 編集キー/
■97648 / inTopicNo.17)  Re[11]: パラレル処理での共通変数
□投稿者/ メタルスライム (5回)-(2021/06/20(Sun) 11:21:48)
2021/06/20(Sun) 17:25:14 編集(投稿者)
2021/06/20(Sun) 17:25:02 編集(投稿者)

すみません、書き直します。
引用返信 編集キー/
■97649 / inTopicNo.18)  Re[12]: パラレル処理での共通変数
□投稿者/ WebSurfer (2269回)-(2021/06/20(Sun) 16:22:07)
No97648 (メタルスライム さん) に返信

> ※自分がコードの構造を理解するために変数名を日本語にしています

日本語の変数は使ったことがない自分にはメチャ読みにくいんですけど・・・ 

しばらく眺めてみましたが、どうしても読む気力が沸いてきません。

英語で書き直すって・・・ 今更無理ですよね
引用返信 編集キー/
■97650 / inTopicNo.19)  Re[12]: パラレル処理での共通変数
□投稿者/ WebSurfer (2270回)-(2021/06/20(Sun) 18:38:04)
No97648 (メタルスライム さん) に返信

> すみません、書き直します。

日本語の識別子名で書いたコードはそのまましておいてはいかがでしょう。

抵抗なく読める人がいて、コメントをくれるかもしれませんし。
引用返信 編集キー/
■97705 / inTopicNo.20)  Re[13]: パラレル処理での共通変数
 
□投稿者/ メタルスライム (6回)-(2021/07/02(Fri) 04:04:17)
お世話になります

実は記載のコードがうまく動かなかったので削除しました
うまく動いたコードは次です



●非同期処理及び並列処理 は WebSurfer さんの 下記の要領で実装

Await Task.Run(Function() Parallel.[For](0, 5, Function(n) CSharpImpl.__Assign(results(n), Work(n))))


●UI更新は

プロセス共通の変数を宣言を非同期内で変更 メインスレッドで参照
プロセス共通の変数は syncLock  でくるんでいます


●UI更新は

Timer ではうまく動作しなかったため

非同期動作中にメインスレッドで loop を回しながら
プロセス共通の変数を読み込んでUI更新





上記で、今の所うまく動いていますので報告させていただきます

最初添付したコードは一部の環境ではうまく動作しましたが
うまく動作しない環境がありましたので
環境に依存する不具合がありました

よろしくおねがいします
引用返信 編集キー/

このトピックをツリーで一括表示

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -