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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

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

Windows Forms

Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。

非同期処理

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

Q&A

解決済

2回答

4130閲覧

Task.Runを使用せずにasync-awaitだけで非同期処理を実現できるのか

dsnmae

総合スコア5

C#

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

Windows Forms

Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。

非同期処理

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

1グッド

0クリップ

投稿2022/06/09 00:44

編集2022/06/09 08:01

参考にしたサイトは下記になります。

https://www.kekyo.net/2015/06/04/4735

また、async-awaitの使い方を調べていてこの記事にたどり着いた場合は、「基本的にTask.Runは使わない」と言う事を頭の片隅に置いておいて下さい。特殊な場合を除いて、明示的にTask.Runでワーカースレッドを操作する必要はありません。非同期メソッドが返すTaskクラスをawaitする事に集中すれば、問題なくコードを記述できる筈です。

https://www.kekyo.net/2016/12/06/6186

Task.Run()とか、Task.Factory.StartNew()とか、Task.Start()とか、Task.ContinueWith()を使ってるコードを頻繁に見る(不要なのに)。

ここまで、Task.Run()も、Task.Factory.StartNew()も、Task.Start()も、Task.ContinueWith()も、Task.Wait()も、Task.Resultも出ませんでしたね? これらを使う事はまずありません(断言)。

と記載していますが、記事内ではHTTPClientのReadFromUrlAsync()を利用しているためReadFromUrlAsync()が内部でTask.Runを行って非同期処理になっているだけに見えます。
(ReadFromUrlAsync()を使用した検証はまだ行えていません)
そもそもasyncとawaitだけを書いても自作メソッドでは最終的にawaitを書く事が出来ず「CS1998:この非同期メソッドにはawait演算子がないため、同期的に実行されます」の警告が出ます。
Task.Run()を使わないと非同期処理が出来ないか一瞬で抜ける挙動しか私には書けません。
また、下記の検証用コードではTask.Runを使用していなければどれだけasync-awaitを記述しようが同期処理しているように見えます。
Form内のListViewへファイルをドロップし、txtファイルであればlistviewへ列挙して処理開始と終了をRichTextBoxへ記入するという挙動で作成しました。
Formアプリケーションで非同期処理を行っている間はControlを触るとエラー落ちするため、InvokeRequiredでInvokeの要不要を確認し、エラー回避を行っていますがこのコードでも私の推測と同じ挙動を示しています。
また大量のファイルをあえてドロップする事でFormが固まるかも確認しましたが、Task.Runを使用した場合のみFormが固まりませんでした。

使用Controlは
Form Form1
ListView listViewTextList
RichTextBox richTextBoxResult(AllowDrop = True)
です

C#

1 public Form1() 2 { 3 InitializeComponent(); 4 } 5 //Func<Task>で使用する場合、引数が使用出来ない?ため変数を用意 6 7 /// <summary> 8 /// DragEventArgs 9 /// </summary> 10 private DragEventArgs Temp_e; 11 /// <summary> 12 /// ListView 13 /// </summary> 14 private ListView Temp_ListView; 15 /// <summary> 16 /// 拡張子判定用 17 /// </summary> 18 private string Temp_SelectExtension; 19 20 //----------------------------------------formイベント------------------------------------------ 21 22 private void listViewTextList_DragEnter(object sender, DragEventArgs e) 23 { 24 //ファイルドロップ操作受付 25 e.Effect = DragDropEffects.All; 26 } 27 28 private async void listViewTextList_DragDrop(object sender, DragEventArgs e) 29 { 30 //awaitは使用するが非同期になっていない。 31 await DropDataCheck(e, listViewTextList, ".txt"); 32 } 33 34 //----------------------------------------DragDropイベント------------------------------------------ 35 36 private async Task DropDataCheck(DragEventArgs e,ListView TempListView,string SelectExtension) 37 { 38 //Func<Task>で使用する場合、引数が使用出来ない?ため先に変数へ入れておく 39 Temp_e = e; 40 Temp_ListView = TempListView; 41 Temp_SelectExtension = SelectExtension; 42 43 //TextBoxに処理開始を記載させて動作確認 44 //DropDataCheckはawaitと記述しているがthis.InvokeRequiredはfalseであり、InvokeせずControlに触っても影響がない。 45 InvokeTextBox(richTextBoxResult,"処理を開始しました。"); 46 47 //await時に戻り値へawaitを記載すると書いてあるので検証用にTask<string>で戻している。 48 //※this.InvokeRequiredはfalseであり、InvokeせずControlに触っても影響がない。 49 var data = await DropDataCheckAsync(); 50 51 //下記2行を使用した場合は非同期処理になっている。 52 //※this.InvokeRequiredがTrueとなり、InvokeせずControlに触ればエラーになる。 53 Func<Task> AsyncJob = DropDataCheckAsync; 54 await Task.Run(AsyncJob); 55 } 56 /// <summary> 57 /// Drop 58 /// 「CS1998:この非同期メソッドにはawait演算子がないため、同期的に実行されます」 59 /// </summary> 60 /// <returns></returns> 61 62 private async Task DropDataCheck(DragEventArgs e,ListView TempListView,string SelectExtension) 63 { 64 //Func<Task>で使用する場合、引数が使用出来ない?ため先に変数へ入れておく 65 Temp_e = e; 66 Temp_ListView = TempListView; 67 Temp_SelectExtension = SelectExtension; 68 69 //TextBoxに処理開始を記載させて動作確認 70 //DropDataCheckはawaitと記述しているがthis.InvokeRequiredはfalseであり、InvokeせずControlに触っても影響がない。 71 InvokeTextBox(richTextBoxResult,"処理を開始しました。"); 72 73 //await時に戻り値へawaitを記載すると書いてあるので検証用にTask<string>で戻している。 74 //※this.InvokeRequiredはfalseであり、InvokeせずControlに触っても影響がない。 75 var data = await DropDataCheckAsync(); 76 77 //下記2行を使用した場合は非同期処理になっている。 78 //※this.InvokeRequiredがTrueとなり、InvokeせずControlに触ればエラーになる。 79 Func<Task> AsyncJob = DropDataCheckAsync; 80 await Task.Run(AsyncJob); 81 } 82 /// <summary> 83 /// Drop 84 /// </summary> 85 /// <returns></returns> 86 private async Task<string> DropDataCheckAsync() 87 { 88 //ドロップされたデータがファイルなら取得 89 if (Temp_e.Data.GetDataPresent(DataFormats.FileDrop)) 90 { 91 //ファイルデータのみ列挙 92 string[] DropData = (string[])Temp_e.Data.GetData(DataFormats.FileDrop, false); 93 //列挙先のリスト初期化※非同期確認 94 InvokeListViewClear(Temp_ListView); 95 foreach (var file in DropData) 96 { 97 //フォルダはフォルダで後々処理するので列挙しておく 98 if (Directory.Exists(file)) 99 { 100 InvokeListViewAdd(Temp_ListView,file); 101 } 102 //ファイルは拡張子を確認して必要なデータのみ取得 103 if (File.Exists(file)) 104 { 105 //拡張子の大文字小文字へ対応 106 if (0 == string.Compare(Path.GetExtension(file), Temp_SelectExtension, true)) 107 { 108 InvokeListViewAdd(Temp_ListView, file); 109 } 110 } 111 } 112 } 113 //TextBoxに処理開始を記載させて動作確認 114 InvokeTextBox(richTextBoxResult, "処理を開始しました。\r処理を終了しました。"); 115 116 //var data = await DropDataCheckAsync();の構文にするためだけのreturn 117 //await DropDataCheckAsync();でTaskのreturn;でも同期処理になっている。 118 return "test"; 119 } 120 //----------------------------------------Invokeイベント------------------------------------------ 121 122 public delegate void DelegateUpdateTextBox(RichTextBox SelectTextBox, string WriteData); 123 public delegate void DelegateListViewAdd(ListView SelectListView, string file); 124 public delegate void DelegateListViewClear(ListView SelectListView); 125 126 private void InvokeTextBox(RichTextBox SelectTextBox,string WriteData) 127 { 128 //非同期であればFormのControlを触るとエラーになるためInvokeRequiredで確認 129 if (this.InvokeRequired) 130 { 131 //非同期処理中であればInvokeでエラー回避 132 this.Invoke(new DelegateUpdateTextBox(this.InvokeTextBox), SelectTextBox, WriteData); 133 return; 134 } 135 //同期中ならエラーにならないのでそのまま変更 136 SelectTextBox.Text = WriteData; 137 } 138 private void InvokeListViewAdd(ListView SelectListView,string file) 139 { 140 //非同期であればFormのControlを触るとエラーになるためInvokeRequiredで確認 141 if (this.InvokeRequired) 142 { 143 //非同期処理中であればInvokeでエラー回避 144 this.Invoke(new DelegateListViewAdd(this.InvokeListViewAdd),SelectListView, file); 145 return; 146 } 147 //同期中ならエラーにならないのでそのまま変更 148 SelectListView.Items.Add(file); 149 SelectListView.Items[Temp_ListView.Items.Count - 1].SubItems.Add(Path.GetFullPath(file)); 150 } 151 private void InvokeListViewClear(ListView SelectListView) 152 { 153 //非同期であればFormのControlを触るとエラーになるためInvokeRequiredで確認 154 if (this.InvokeRequired) 155 { 156 //非同期処理中であればInvokeでエラー回避 157 this.Invoke(new DelegateListViewClear(this.InvokeListViewClear), SelectListView); 158 return; 159 } 160 //同期中ならエラーにならないのでそのまま変更 161 SelectListView.Items.Clear(); 162 } 163

私の記述が誤っていて正確に非同期処理となっていないのでしょうか。
それともForm独特の挙動なのでしょうか。
大量のファイルを取得した後に処理を行うため、非同期処理を使用したいのです。

Bongo👍を押しています

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

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

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

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

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

dodox86

2022/06/09 07:15

大したことではないのですけど、タグに付けている"Google Cloud Platform"は関係無いので、外すことをお勧めします。
dsnmae

2022/06/09 08:02

WindowsFormを選択したつもりでしたが誤ってForm違いで選んでいました。 修正しました。
guest

回答2

0

ベストアンサー

参考にしている記事は長いし、質問者さんのコードもよく分からないので、表題に対してのみレスします。

Task.Runを使用せずにasync-awaitだけで非同期処理を実現できるのか

例えば同期メソッドしか提供されてないライブラリで、それを別スレッドで実行したい場合、Task.Run を使わざるを得ないです。

ちなみに、async をメソッドに付与して内部で await を使わないと以下の警告が出ますよね。

warning CS1998: この非同期メソッドには 'await' 演算子がないため、同期的に実行されます。'await' 演算子を使用して非ブロッキング API 呼び出しを待機するか、'await Task.Run(...)' を使用してバックグラウンドのスレッドに対して CPU 主体の処理を実行することを検討してください。

.NET Frameworkにおける非同期処理実装技術の歴史は以下の記事の Figure 3 のようになっているそうです。そういう経緯から勘違い(?)して、Task はもう使わないとかいう記事を目にしますが、それはちょっと違うと思います。

第1回 .NET開発における非同期処理の基礎と歴史
https://atmarkit.itmedia.co.jp/fdotnet/chushin/masterasync_01/masterasync_01_02.html

もう一つ、勘違いしているかもしれないので念のため書いておきます。async というのはただの修飾子です。メソッドが非同期であると指定するとともに内部で await を使えるようにするものです。async を付与したからと言って非同期になるわけではありません。詳しくは以下のドキュメントを見てください。

async (C# リファレンス)
https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/async


【追記】

非同期に切り替えている

そこのところが疑問の原点になっているような気がします。スレッドがどのように使われているかを、メソッドを呼び出したらその中で Thread.CurrentThread.ManagedThreadId を調べるとそのあたりの疑問が解けるのではないかと思います。

具体的には、例えば以下の Windows Forms アプリで、

namespace WinFormsApp2 { public partial class Form2 : Form { public Form2() { InitializeComponent(); } // 非同期メソッド TimeCosumingMethod1 を呼び出す private async void button1_Click(object sender, EventArgs e) { label1.Text = $"メインスレッド ID: {Thread.CurrentThread.ManagedThreadId}, IsBackground: {Thread.CurrentThread.IsBackground} / "; label2.Text = ""; label2.Text = await TimeCosumingMethod1(); label1.Text += $"{Thread.CurrentThread.ManagedThreadId}, {Thread.CurrentThread.IsBackground}"; } // 同期メソッド TimeCosumingMethod2 を呼び出す private async void button2_Click(object sender, EventArgs e) { label1.Text = $"メインスレッド ID: {Thread.CurrentThread.ManagedThreadId}, IsBackground: {Thread.CurrentThread.IsBackground} / "; label2.Text = ""; label2.Text = await Task.Run(() => TimeCosumingMethod2()); label1.Text += $"{Thread.CurrentThread.ManagedThreadId}, {Thread.CurrentThread.IsBackground}"; } // 非同期メソッド private async Task<string> TimeCosumingMethod1() { string s = $"TimeCosumingMethod1 スレッド ID: {Thread.CurrentThread.ManagedThreadId}, IsBackground: {Thread.CurrentThread.IsBackground} IN / "; await Task.Delay(3000); return s + $"{Thread.CurrentThread.ManagedThreadId}, {Thread.CurrentThread.IsBackground} OUT"; } // 同期メソッド private string TimeCosumingMethod2() { string s = $"TimeCosumingMethod2 スレッド ID: {Thread.CurrentThread.ManagedThreadId}, IsBackground: {Thread.CurrentThread.IsBackground} IN / "; Thread.Sleep(3000); return s + $"{Thread.CurrentThread.ManagedThreadId}, {Thread.CurrentThread.IsBackground} OUT"; } } }

非同期メソッド TimeCosumingMethod1 を呼び出した場合、下の画像にある通り ManagedThreadId は全て 1 で UI スレッドになっています。

イメージ説明

同期メソッド TimeCosumingMethod2 を Task.Run を使って呼び出した場合は、TimeCosumingMethod2 がスレッドプールから別スレッドを取得して実行されるので、下の画像の通りManagedThreadId が異なります(7 になっています)。

イメージ説明

「非同期に切り替えている」ということではないのが分かるでしょうか?

投稿2022/06/09 01:53

編集2022/06/09 03:29
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

dsnmae

2022/06/09 02:29

ありがとうございます。 すぐに読んでみます。
dsnmae

2022/06/09 08:32

元々非同期処理を使用しようとしたのはFormアプリケーションが処理中に止まらないようにするためにする方法を探していると非同期処理を使うと書かれていたからでした。 そのため動作が停止しない方法としてasync-awaitを使用していましたが、根本的な勘違いとしてFormアプリケーションの動作が止まらないようにするには同期非同期というより別スレッドで処理を行えているかどうか、という事を理解していませんでした。 また、私が検証として使用していたthis.InvokeRequiredも具体的な動作の意味が分かっておらず勘違いをしていました。 タイトルもその違いを理解していないので意味不明なタイトルになっています。 >スレッドがどのように使われているかを、メソッドを呼び出したらその中で Thread.CurrentThread.ManagedThreadId を調べるとそのあたりの疑問が解けるのではないかと思います。 検証用コードありがとうございます。 先ほど自身で記載したコードでも同じように使用してみて前提から間違えていたと分かりました。 そもそもスレッドやスレッドプールの知識すら曖昧だったため再度1から順序だてて覚えなおします。
guest

0

ReadFromUrlAsync()を使用した検証はまだ行えていません

ReadFromUrlAsync()を使っていないことが根本的に間違いなのではないでしょうか。

引用文は、あくまで「非同期メソッド」を呼び出した場合の話で、同期的な処理から自力で非同期処理を生み出す話には触れていません。

投稿2022/06/09 00:48

編集2022/06/09 01:32
maisumakun

総合スコア145183

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

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

maisumakun

2022/06/09 01:11 編集

> ReadFromUrlAsync()が内部でTask.Runを行って非同期処理になっているだけに見えます。 ReadFromUrlAsync()の中身を気にする必要はありません。引用文中にも「明示的に」とあるように、自分でTask関係を呼ばないという話しかしていません。
dsnmae

2022/06/09 01:17

先ほどの掲載URL(できる!C#で非同期処理(Taskとasync-await))にある第二章. どこからが非同期処理となるのか では自作メソッドであるDownloadAsyncメソッドが非同期処理として扱われていますがこれは誤りという事でしょうか。 また、コンソールアプリケーションの非同期処理 の項目では自作のasync Main関数とDownloadAsync関数もTask.Runを使用せずに非同期になっていると見受けられる記載があるのですがこれはコンソールアプリケーション専用の挙動なのでしょうか。
maisumakun

2022/06/09 01:56 編集

> 自作メソッドであるDownloadAsyncメソッドが非同期処理として扱われていますがこれは誤りという事でしょうか。 誤りではありません。非同期処理を呼んでいるから非同期処理になっています(逆に、どういう論理から「誤り」と判断したのかが気になります)。
maisumakun

2022/06/09 01:24

> 自作のasync Main関数とDownloadAsync関数もTask.Runを使用せずに非同期になっていると見受けられる記載があるのですが こちらも同じで、すでに非同期になったメソッドを呼んでいるので自身も非同期となるだけの話です。
maisumakun

2022/06/09 01:25

> Task.Run()を使わないと非同期処理が出来ないか一瞬で抜ける挙動しか私には書けません。 すでに非同期となったメソッドを呼んでください、という話です。
dsnmae

2022/06/09 02:04

>こちらも同じで、すでに非同期になったメソッドを呼んでいるので自身も非同期となるだけの話です。 ReadFromUrlAsync()を最終的に呼び出すので async Main→DownloadAsync→ReadFromUrlAsyncという流れでメソッドを呼び出す流れの中で実行もしていないReadFromUrlAsyncが非同期であると理解してasync MainとDownloadAsyncを非同期に切り替えているという事ですか?
kikukiku

2022/06/09 02:06

dsnmaeさんへ ソースは長くてちゃんと読んでいませんが、 例えば、this.Invokeは同期メソッドです。 このメソッドは実行が終了するまでブロックします。 動作をわかるようにすることが目的であれば、ログを使った方が良いです。
maisumakun

2022/06/09 02:18 編集

> 実行もしていないReadFromUrlAsyncが非同期であると理解して コンパイラが処理する段階でasyncを解釈していきます。
maisumakun

2022/06/09 02:22 編集

> async MainとDownloadAsyncを非同期に切り替えているという事ですか? 非同期なメソッドをawaitで呼び出すから自身も非同期になる、というだけの話で、「切り替える」などという概念は存在しないと思うのですが。
dsnmae

2022/06/09 02:21

@kikukikuさん >this.Invokeは同期メソッドです。 formアプリケーションではControlのデータを変更する際に別スレッドからプロパティを変更する事が出来ません。 InvalidOperationExceptionのエラーが発生します。 今回であればrichtextboxのtextプロパティを変更して画面上に処理状況を通知する方法を採用しています。 そのためにInvokeを使用するかどうかの判定を使用しています。
dsnmae

2022/06/09 02:28

@maisumakun >非同期に「切り替える」という単語を使う時点で、何かしら理解がおかしいような印象を受けます スレッドと非同期処理がうまく理解出来ていないようでした。 もう一つの回答にある解説を今読み込んで一度質問を整理しています。
退会済みユーザー

退会済みユーザー

2022/06/09 02:39

kikukiku さん> > 下記のように非同期メソッドがありますよ。 それはデリゲートを使っての非同期処理に使うものではないのですか? 私の回答で紹介した記事「第1回 .NET開発における非同期処理の基礎と歴史」の Asynchronous Programming Model のものだと思いますけど。
退会済みユーザー

退会済みユーザー

2022/06/09 03:05 編集

dsnmae さん> > スレッドと非同期処理がうまく理解出来ていないようでした。 メソッドを呼び出したらその中で Thread.CurrentThread.ManagedThreadId を調べるとどうなっているか分かると思います。
dsnmae

2022/06/09 08:44

@maisumakunさん >コンパイラが処理する段階でasyncを解釈していきます。 >非同期なメソッドをawaitで呼び出すから自身も非同期になる、というだけの話で、「切り替える」などという概念は存在しないと思うのですが。 私が大きな勘違いをしていた事にようやく気づきました。 長いためもう一つの回答へ投稿しましたが、投稿いただいた内容の意味が理解出来ました。 タイトルと内容が誤解に基づく内容のため、返答が大きくズレていたのは申し訳ありません。 @kikukiku そちらのメソッドについても調べてみます。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問