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

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

新規登録して質問してみよう
ただいま回答率
85.37%
排他制御

排他制御とは、特定のファイル・データへのアクセスや更新を制御することです。特にファイルやデータベースへ書き込みを行う際、データの整合性を保つため別のプログラムによる書き込みを一時的に制御することを指します。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Q&A

解決済

3回答

3073閲覧

C#Invokeを用いた非同期処理の待機

acro_shachi

総合スコア1

排他制御

排他制御とは、特定のファイル・データへのアクセスや更新を制御することです。特にファイルやデータベースへ書き込みを行う際、データの整合性を保つため別のプログラムによる書き込みを一時的に制御することを指します。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

0グッド

1クリップ

投稿2023/01/18 18:55

編集2023/01/23 13:22

前提

プログラムの知識があまりないもので不明点は掘り下げて聞いていただけると助かります。

C# WPF windows FormでInvokeを使用したcallback関数を用いて、非同期処理を行うプログラムを書いています。
ワーカスレッドで取り扱う値が貰い物のref構造体のため、Taskやasync処理などでの処理は難しい状態です。

完了通知用のcallback関数として後述のコードを使っており。
メインスレッドとは異なるスレッドにて、モータを動かす処理を行う関数を実行するたびに下記のcallback関数が呼び出されるようになっています。
(追記)モータを動かすためのライブラリ内に関数は複数種類含まれており、他者が作成したものを使用しています。(内部のコードは参照不可だった。)
①モータを指定の角度分、相対的に動かす②モータを入力した絶対角度になるよう動かす③動作途中で強制停止する(強制停止用のボタンをGUIにも用意しておきたい)
いずれの関数(コマンド)を実行しても、同じcallbackを用いて関数実行(それに応じたモータの移動)完了を待機しようとしております。

問題としては「return true」がcallback通知より早く返されておりいるようで、コマンド処理を待機することができず、解決方法が見つからず困っています。

現在の処理フローは下記のようなイメージです。
(追記箇所)
目的:絶対角度で指定した値と、実際のモータ角度のずれを複数回はかり、最終的に位置を合わせるために相対角度で微調整することを考えている。
0:モータの状態を確認する。モータの状態からコマンドを実行する回数および実行するコマンドの種類を決定する。

①適当な関数を実行する。
②別スレッド(ワーカスレッド)上で処理が始まる。
③~~callback関数の(?)~~コマンド開始を行うと、メインスレッド上でtrueが帰る。(追記、モータが動作を開始した時点でコマンドの受付を行ったといった意図でtrueが返っていた模様。callback関数のtrueが帰るはご認識だった。)
④次の関数が継続して開始される。(本来はcallbackが帰ってくるまでは待機してほしい)以下①~③同様
⑤時間をおいて初めの関数で実施したモータ動作コマンドの処理が完了する。
⑥ ①で実行されたcallback関数が帰ってくる。(この時点で1回分の動作しか行われていない。)

実現したいこと

⑤(Invoke内の完了通知)を、現在の②と③の間(各関数の実行完了通知後に次の関数に移る処理シーケンス)
に実施できるような処理をメインスレッドにて行いたい。

~~(追記)
処理回数については、2回で終わるとは限らず、2回以上複数回繰り返すケースを想定している。
処理の都度、継続が必要か、もしくは終了してよいか判断するように追記していく予定。~~→目的に記載した内容の通り。

該当のソースコード

using function;//関数ライブラリの呼び出し namespace TestGUI { public partial class Control : Form { public function.func lib; //初期化 public ManualControl() { InitializeComponent(); lib = new func();//dllの読み込み lib.SetCallback(CallBack);//dllライブラリ側の処理にcallback関数を登録 } //callback関数 //public bool CallBack(Work.Complete fin) Workは誤り public bool CallBack(func.Complete fin) { this.Invoke((Action)(() => { textBox_Result.Text = fin.Result.ToString() })); return true; } //main処理関数 private void buttonMain_Click(object sender, EventArgs e) { //0番の処理(移動回数、移動コマンドの内容の決定) //1つ目の処理 textBox1.Text = lib.calc1(enum_name, integer, ref_struct).ToString();//ライブラリ側のモータ回転処理 //待機処理を挟みたい箇所 //2つ目の処理 textBox2.Text = lib.calc2(enum_name, integer, ref_struct).ToString(); } } }

試したこと

・ButtonMain関数のTask処理への変更(ref構造体が含まれる処理には使用不可、原因は把握できていない。 CS4012)
・Callback関数そのもののasync/await処理への変更(bool型使用不可の旨のエラー文発生 CS0127)

補足情報(FW/ツールのバージョンなど)

・.NET framework 4.7.2
ここにより詳細な情報を記載してください。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2023/01/18 23:47

まともに書かれてないので内容読んでませんが、BeginInvokeだと非同期で実行できます。 Control派生クラスのInvoke/BeginInvokeメソッドならUIスレッドで実行されます。 Invokeだと非同期にならないので、デッドロック(例外かも)に注意してください。
acro_shachi

2023/01/19 00:01

早速のご回答ありがとうございます。 Invokeで記載されてる箇所を、BeginInvokeで書き換えるという事で認識しました。こちら試してみます。 callbackについては、初期化にて別クラスにて使用する為に登録する関数を用意しており、別クラスではintとref構造体を引数としています。 内容については諸事情で多くがお見せできず、他にも至らないところが多々あるかと思いますが恐縮です。
退会済みユーザー

退会済みユーザー

2023/01/19 00:03

内容「全く」読んでないので書き換えるのはあなたの自由ですよ。
YAmaGNZ

2023/01/19 02:02

buttonMain_Clickでの「1つ目の処理」の処理が完了したら「2つ目の処理」を行いたいってことですか? lib.calc1の内部でスレッドを起こして完了時にCallBackが呼ばれるということですよね? それならCallBack内でのInvokeは何も関係ないのでは?
退会済みユーザー

退会済みユーザー

2023/01/19 02:23

> ワーカスレッドで取り扱う値が貰い物のref構造体のため、Taskやasync処理などでの処理は難しい状態です。 そのあたり意味不明ですが、要するにデリゲートを使う方法(以下の記事の中の Asynchronous Programming Model)にしたいということですか? .NET開発における非同期処理の基礎と歴史 https://atmarkit.itmedia.co.jp/fdotnet/chushin/masterasync_01/masterasync_01_02.html
acro_shachi

2023/01/19 03:35

YAmaGNZさん >buttonMain_Clickでの「1つ目の処理」の処理が完了したら「2つ目の処理」を行いたいってことですか? こちら認識の通りです。 実態としては、外部に繋がれたモーターを物理的に動かしていて、移動が完了したらcallback内のinvoke部分が通知を出すのですが、 確認したところreturn trueが先に帰ってしまっているため、lib.calc1の動作中にlib.calc2が実行開始・実行完了(モーターは移動中はコマンドを受け付け内容になっており、現実ではcalc1のみで終了)してしまいます。 SurferOnWwwさん >そのあたり意味不明ですが、要するにデリゲートを使う方法(以下の記事の中の Asynchronous Programming Model)にしたいということですか? 手法は問わないのですが、先述の通り、コマンドを連続で実行する際に、コマンドごとに完了を待機をしたいと考えておりました。 いただいたリンクの実行イメージでも出来るかと思いますが、後ほど確認します。
YAmaGNZ

2023/01/19 05:01

lib.calc1を呼び出すと即座にメソッドから帰ってきて、モーターが動き始める モーターが動ききったらCallBackが呼ばれるのではないのですか? 「return trueが先に帰ってしまっている」とはどういうことなのでしょうか?
kikukiku

2023/01/19 05:29

callbackでの終了通知ではなく、 スレッドが終了すること(メソッドが終了すること)で終了通知をする方式には 変更できないのでしょうか? 下記の例では、引数も渡せますし、返り値も取れます。 private async void button1_Click(object sender, EventArgs e) { button1.Enabled = false; var p1 = "p1"; var test1 = await Task<string>.Factory.StartNew(thread_cal1, (object)p1); var p2 = "p2"; var test2 = await Task<string>.Factory.StartNew(thread_cal2, (object)p2); button1.Enabled = true; } private string thread_cal1(object p1) { var ret = "test1"; System.Threading.Thread.Sleep(3000); return ret; } private string thread_cal2(object p2) { var ret = "test2"; System.Threading.Thread.Sleep(3000); return ret; }
YAmaGNZ

2023/01/19 07:20

だからモーターを動かすライブラリ(?)の仕様がCallBackでの終了通知なんでしょ? CallBackが呼ばれたら終了なんだからそこでフラグなり立てて、それが立つまで待機とかすればいいのでは?
kikukiku

2023/01/19 07:25

あーー、なるほど。ライブラリがそういう仕様なら確かにそうですね。 質問文にそのあたりの実装が書かれていないので サンプル作るにしても作れないんですよね。
YAmaGNZ

2023/01/19 07:32

多分他者が作ったものを利用してると思うんですよね。 CallBackが別スレッドで呼ばれて、InvokeかけてUIに対してアクセスしないといけないと理解してコードが書けるのに、モーター動かす部分を別スレッドで動作させて待機する部分を書けないってあまり考えられないんで
退会済みユーザー

退会済みユーザー

2023/01/19 07:38

まともに書いてない人にまともに答えることはないと思いますよ。あんまり書きたくなかったですが、多分こんなのです。 using System; using System.Threading; using System.Windows.Threading; namespace ConsoleApp1 { class Program { static void Hoge(Action callback) { callback(); } static void Main(string[] args) { Console.WriteLine($"start-Main({Thread.CurrentThread.ManagedThreadId})"); var dispatcher = Dispatcher.CurrentDispatcher; dispatcher.BeginInvoke(new Action(() => { Console.WriteLine($"start-Target({Thread.CurrentThread.ManagedThreadId})"); Hoge(() => { Console.WriteLine($"start-callback({Thread.CurrentThread.ManagedThreadId})"); dispatcher.BeginInvoke(new Action(() => { Console.WriteLine($"start-invoke_in_callback({Thread.CurrentThread.ManagedThreadId})"); dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal); Console.WriteLine($"end -invoke_in_callback({Thread.CurrentThread.ManagedThreadId})"); })); Console.WriteLine($"end -callback({Thread.CurrentThread.ManagedThreadId})"); }); Console.WriteLine($"end -Target({Thread.CurrentThread.ManagedThreadId})"); })); Dispatcher.Run(); Console.WriteLine($"end -Main({Thread.CurrentThread.ManagedThreadId})"); Console.ReadLine(); } } }
acro_shachi

2023/01/19 14:23 編集

コメントとアドバイスありがとうございます。 まともな質問文もかけずすみません。読み解くのに苦労されることすら考えられていませんでした。失礼なことをしました。 日中は実行環境がなかったので、今から試してみます。 >>だからモーターを動かすライブラリ(?)の仕様がCallBackでの終了通知なんでしょ? >>多分他者が作ったものを利用してると思うんですよね。 想像の通り、他者の作成したdllライブラリを使用しています。私自身はプログラミングはほぼ初心者です。 恥ずかしながらフラグ管理の方法すらわかっていませんが、さすがに自分で調べながら進めてみます。 改めてありがとうございます。 追記、WPFでなくてwindows formで作成をしていましたが、 dameoさんに、丁寧なコードを書いていただいたのでWPFも触ってみます。
退会済みユーザー

退会済みユーザー

2023/01/19 14:25

いや、回答者さん向けに書いたものだし、コンソールアプリですよ。
acro_shachi

2023/01/19 14:31

そうだったんですね。また文意が読み取れずすみません。 さすがにGUIで操作する書き方ではないことは理解できたので、WPFで流用ができないか試しに動かしていました。
guest

回答3

0

C#初心者なので,話そのものを勘違いしているかもしれませんが…

  • 「1つ目の処理」が終わった際にコールバック関数が呼ばれるのであって,
  • 「1つ目の処理」が終わってから「2つ目の処理」を実施したいのであれば,

「2つ目の処理」を実施するための記述をコールバック関数の中に書けば良いのでは…? とか思うのですが.

C#

1private void buttonMain_Click(object sender, EventArgs e) 2{ 3 // (A) 4 //この辺で lib.calc1() 実施中に余計な操作をできないように,コントロールをDisable状態にしたりとかしておく. 5 //処理中に再度ボタン buttonMain を押されのはまずいのだろうから buttonMain.Enable = false; とか. 6 //(他にも,「処理中にフォームが閉じられるのは困る」みたいな話もあるかも) 7 8 //1つ目の処理を開始 9 textBox1.Text = lib.calc1(enum_name, integer, ref_struct).ToString();//ライブラリ側のモータ回転処理 10} 11 12//callback関数 13public bool CallBack(func.Complete fin) 14{ 15 //lib.calc1() 完了時にここにくるという話ならば,ここで 16 //【{ 前記(A)と逆の処理 ,2つ目の処理たる lib.calc2() }を実施する処理】 17 //を BeginInvoke() でもすればよいのでは. 18 19 return true; 20} 21

投稿2023/01/20 01:52

fana

総合スコア11954

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2023/01/20 01:56 編集

「そんな話をしてるわけじゃねぇんだよ,引っ込んでろ」とかいう場合には, その旨(というか,だったらどんな話をしているのか?という点)を明示的に述べていただければ良いかと思います(:他の有識者な方々が問題についてより把握するための情報が増えるという意味で)
acro_shachi

2023/01/21 07:50 編集

アドバイスとコメントありがとうございます。 質問内容だと不足している内容がありました。 補足していくと、処理の回数は2回以上(回数不定)、処理を追加で行っていく必要があるかどうかは処理の都度決めていくよう考えていました。 私の認識が正しければ、fanaさんの方法だとcallbackの再登録が、処理の都度必要になるのかなと考えております。 (そのあたりもはっきりと決めていない、意識できていなかったと反省しています。 質問者さんのように、2回処理ができればよいと誤解を招くかもしれないので、こちらも質問文に追記しようと思います。) >>その旨(というか,だったらどんな話をしているのか?という点)を明示的に述べていただければ良いかと思います(:他の有識者な方々が問題についてより把握するための情報が増えるという意味で) 丁寧にご説明ありがとうございます。今後また質問することがあった場合のためにも意識して、必要な情報を記載してみます。
fana

2023/01/22 01:49

> 処理を追加で行っていく必要があるかどうかは処理の都度決めていく その判断というのは一体いつできるものなのか全く謎ですが, 単一のコールバックではできないという想定をされているのであれば「何故できないのか?」という事情を説明していく必要があるように思います. (処理完了時に呼ばれるものであるコールバックが呼ばれた時点では判断が付かない,ということであれば,そこらへんの事情を知らない側からすると不思議な話に思える)
acro_shachi

2023/01/23 12:50

fanaさん。ご指摘ありがとうございます。文章の流れに沿って回答します。 >>その判断というのは一体いつできるものなのか全く謎ですが, 今回の質問では、モータを動かす前の状態を取得して、目標値との差に応じて、ライブラリのコマンド実行回数(または内容)を決定してから、1つ目のコマンドに繋げるつもりでした。こちらも後出しの情報になっていました。 >>単一のコールバックではできないという想定をされているのであれば「何故できないのか?」という事情を説明していく必要があるように思います. 自分も知識がまとまってない中で回答していたため、説明が適切にできない中で回答しておりました。明確な根拠はありませんでした。 情報が後出しばかりだった点は、その後fanaさんが記載されている以下の文章に通じていると思います。 >>(処理完了時に呼ばれるものであるコールバックが呼ばれた時点では判断が付かない,ということであれば,そこらへんの事情を知らない側からすると不思議な話に思える) 今回の質問自体、自身が注目している点しか記載していなかったので、自分で見返しても後出しの情報ばかりでした。 事情を把握していない人への説明には、不誠実な質問の仕方だと味わっています。 失礼なことをして、すみません。ご指摘ありがとうございます。
fana

2023/01/24 00:54

誰かに対して「失礼」とかいうよりも,もっと単純に,ご自身の抱えている都合に見合う情報が得られ難い状況というのはご自身にとっても不利益なのでは? と.
guest

0

読んでいてライブラリとUIの制約の折り合いが難しいと感じました。このようなケースでは問題をシンプルにするために、UIとロジック部分を分離して考えるとよいです。

外部ライブラリの管理クラスを作る
リクエスト関数で lib.calc1(enum_name, integer, ref_struct) の戻り値をリターン
ステータス取得関数で、コールバック待機して戻り値をリターン。このときスレッドからの通知待ちにはEventWaitHandle を用いる。

UI側はハンドラ関数内で await Task.Run()で管理クラスのメソッド(リクエスト関数、ステータス取得関数)をコールすると、ブロッキングせずに前の動作が完了後に次の動作に移れます。

ref構造体でTask.Runが呼べないという制約がよく分かりませんが、問題の構造体は管理クラスに閉じ込めて、UIクラスはそれを示すインデックスなりを引数にすれば問題ないでしょう。

投稿2023/01/22 07:20

編集2023/01/22 11:39
ruruucky

総合スコア18

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

acro_shachi

2023/01/23 16:39

ご回答ありがとうございます。 機能を分離して設計するというところは意識が向けておらず、とにかく動けばいいと考えておりました。 正直、まだ理解できていないところもありますが、調べながら設計をしてみようと思います。
guest

0

ベストアンサー

コールバックが呼ばれるのを待機するのであれば
AutoResetEvent クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.threading.autoresetevent?view=netframework-4.7.2
を使うといいのではないでしょうか。

CSharp

1using System; 2using System.Threading; 3using System.Windows.Forms; 4 5public partial class ManualControl : Form 6{ 7 AutoResetEvent callbackEvent = new AutoResetEvent(false); 8 string resultString; 9 10 public ManualControl() { 11 InitializeComponent(); 12 lib = new func();//dllの読み込み 13 lib.SetCallback(CallBack);//dllライブラリ側の処理にcallback関数を登録 14 } 15 16 public bool CallBack(Work.Complete fin) { 17 // ここで Invoke するとデッドロックになるのでフィールドに保存 18 resultString = fin.Result.ToString(); 19 // 待機状態のスレッドを解放 20 callbackEvent.Set(); 21 return true; 22 } 23 24 private void buttonMain_Click(object sender, EventArgs e) { 25 //1つ目の処理 26 textBox1.Text = lib.calc1(enum_name, integer, ref_struct).ToString(); 27 textBox1.Refresh(); 28 callbackEvent.WaitOne(); // callbackEvent.Set() が実行されるのを待つ 29 textBox_Result.Text = resultString; // 保存した値を表示 30 textBox_Result.Refresh(); 31 32 //2つ目の処理 33 textBox2.Text = lib.calc2(enum_name, integer, ref_struct).ToString(); 34 textBox2.Refresh(); 35 callbackEvent.WaitOne(); 36 textBox_Result.Text = resultString; 37 textBox_Result.Refresh(); 38 } 39}

投稿2023/01/19 15:25

KOZ6.0

総合スコア2696

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

acro_shachi

2023/01/19 17:21

回答ありがとうございます。 自分のプログラム上で試してみましたが、28行目で動作が止まり捜査を受け付けなくなりました。 textBox1には文字列が表示されて、ブレークポイントがそこで帰ってこなくなります。
KOZ6.0

2023/01/19 22:58 編集

28 行目は CallBack メソッドの callbackEvent.Set(); が呼ばれるまで待機します。 別スレッドから呼ばれているなら大丈夫のはずですが、ひょっとして、別スレッドから Invoke を介して呼び出されるのでしょうか?
KOZ6.0

2023/01/19 23:23

28行目を while (!callbackEvent.WaitOne(100)) { Application.DoEvents(); } に変えるとどうなりますか?
acro_shachi

2023/01/21 07:56 編集

返信遅くなりすみません。追加でのコメントありがとうございます。 >>28行目をwhile (!callbackEvent.WaitOne(100)) { Application.DoEvents(); }に変えるとどうなりますか? テスト用のコードでは、28,35行目を書き換えたところ、アドバイス通りの内容でうまくいきましたので、実物で想定通りに処理が動作するかを確認してみます。
acro_shachi

2023/01/23 12:37

KOZ6.0さん、お待たせしました。 回答の内容にて、実際の動作確認を行って、問題なく動作を待機できました。 自分の質問には説明不足な個所も相当数あったため、他の回答を頂いた方への返信等を行ってから、ベストアンサーにさせていただこうと思います。 本当に助かりました。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問