[{"data":1,"prerenderedAt":2377},["ShallowReactive",2],{"content-query-08rnFa7clR":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"tags":11,"rowTypeId":17,"sitemap":18,"body":19,"_type":2371,"_id":2372,"_source":2373,"_file":2374,"_stem":2375,"_extension":2376},"/articles/tech/blazor/blazor-wasm-multithreading","blazor",false,"","Blazor WebAssemblyでマルチスレッドを有効化してUIフリーズを解消する","Blazor WebAssemblyでWasmEnableThreadsを有効化し、音楽生成をバックグラウンドスレッドにオフロードしてUIフリーズを解消した実例です。COOP/COEPの必要性、Channel\u003CT>によるストリーミング再生、PeriodicTimerへの移行で踏んだ不具合も紹介します。","2026-04-18",[12,13,14,15,16],"C#","Blazor","WebAssembly","マルチスレッド","PICOM",1,{"loc":4,"lastmod":10,"priority":17},{"type":20,"children":21,"toc":2361},"root",[22,30,36,55,95,100,120,126,139,190,203,210,227,233,245,250,327,332,338,343,377,382,387,392,854,859,864,877,890,915,922,927,933,1711,1717,1956,1961,1967,1972,1991,2013,2269,2275,2280,2285,2290,2355],{"type":23,"tag":24,"props":25,"children":27},"element","h2",{"id":26},"背景と目的",[28],{"type":29,"value":26},"text",{"type":23,"tag":31,"props":32,"children":33},"p",{},[34],{"type":29,"value":35},"PICOMでは、音を生成しながらピアノロールなどGUIの描画処理を走らせる必要があります。しかし、単純に非同期処理をするだけでは、厳密なマルチスレッド処理にはならず、UIスレッドで音楽の生成をしてしまいます。その結果、音楽生成中にUIがフリーズしていた問題が発生しました。",{"type":23,"tag":31,"props":37,"children":38},{},[39,41,53],{"type":29,"value":40},"そこで、",{"type":23,"tag":42,"props":43,"children":45},"tooltip",{"content":44},"Blazor WebAssemblyで本物のOSスレッドを有効化するMSBuildプロパティ",[46],{"type":23,"tag":47,"props":48,"children":50},"code",{"className":49},[],[51],{"type":29,"value":52},"WasmEnableThreads",{"type":29,"value":54}," を有効化し、音楽生成をバックグラウンドスレッドにオフロードすることで、UI描画と音楽生成を分離する対策を行いました。",{"type":23,"tag":56,"props":57,"children":58},"summary-box",{},[59],{"type":23,"tag":31,"props":60,"children":61},{},[62,64,69,71,77,79,85,87,93],{"type":29,"value":63},"Blazor WebAssembly の ",{"type":23,"tag":47,"props":65,"children":67},{"className":66},[],[68],{"type":29,"value":52},{"type":29,"value":70}," を有効化し、",{"type":23,"tag":47,"props":72,"children":74},{"className":73},[],[75],{"type":29,"value":76},"Channel\u003CT>",{"type":29,"value":78}," による Producer-Consumer パターンで音声のストリーミング再生を実現しました。COOP/COEP ヘッダーの設定や、",{"type":23,"tag":47,"props":80,"children":82},{"className":81},[],[83],{"type":29,"value":84},"System.Timers.Timer",{"type":29,"value":86}," から ",{"type":23,"tag":47,"props":88,"children":90},{"className":89},[],[91],{"type":29,"value":92},"PeriodicTimer",{"type":29,"value":94}," への移行で踏んだ不具合の解決も紹介します。",{"type":23,"tag":24,"props":96,"children":98},{"id":97},"前提環境",[99],{"type":29,"value":97},{"type":23,"tag":101,"props":102,"children":103},"ul",{},[104,110,115],{"type":23,"tag":105,"props":106,"children":107},"li",{},[108],{"type":29,"value":109},"Blazor WebAssembly",{"type":23,"tag":105,"props":111,"children":112},{},[113],{"type":29,"value":114},".NET 10.0",{"type":23,"tag":105,"props":116,"children":117},{},[118],{"type":29,"value":119},"音声ライブラリ: SoundMaker v3.0.0(自作の音生成ライブラリです！)",{"type":23,"tag":24,"props":121,"children":123},{"id":122},"wasmenablethreads-の有効化",[124],{"type":29,"value":125},"WasmEnableThreads の有効化",{"type":23,"tag":31,"props":127,"children":128},{},[129,131,137],{"type":29,"value":130},"まず、",{"type":23,"tag":47,"props":132,"children":134},{"className":133},[],[135],{"type":29,"value":136},".csproj",{"type":29,"value":138}," ファイルに以下を追加します。",{"type":23,"tag":140,"props":141,"children":145},"pre",{"className":142,"code":143,"language":144,"meta":7,"style":7},"language-xml shiki shiki-themes vitesse-dark","\u003CWasmEnableThreads>true\u003C/WasmEnableThreads>\n","xml",[146],{"type":23,"tag":47,"props":147,"children":148},{"__ignoreMap":7},[149],{"type":23,"tag":150,"props":151,"children":153},"span",{"class":152,"line":17},"line",[154,160,165,170,176,181,185],{"type":23,"tag":150,"props":155,"children":157},{"style":156},"--shiki-default:#666666",[158],{"type":29,"value":159},"\u003C",{"type":23,"tag":150,"props":161,"children":163},{"style":162},"--shiki-default:#4D9375",[164],{"type":29,"value":52},{"type":23,"tag":150,"props":166,"children":167},{"style":156},[168],{"type":29,"value":169},">",{"type":23,"tag":150,"props":171,"children":173},{"style":172},"--shiki-default:#DBD7CAEE",[174],{"type":29,"value":175},"true",{"type":23,"tag":150,"props":177,"children":178},{"style":156},[179],{"type":29,"value":180},"\u003C/",{"type":23,"tag":150,"props":182,"children":183},{"style":162},[184],{"type":29,"value":52},{"type":23,"tag":150,"props":186,"children":187},{"style":156},[188],{"type":29,"value":189},">\n",{"type":23,"tag":31,"props":191,"children":192},{},[193,195,201],{"type":29,"value":194},"これにより、",{"type":23,"tag":47,"props":196,"children":198},{"className":197},[],[199],{"type":29,"value":200},"Task.Run",{"type":29,"value":202}," でバックグラウンドスレッドへの処理オフロードが可能になります。",{"type":23,"tag":204,"props":205,"children":207},"h3",{"id":206},"注意点1coopcoep-ヘッダーが必要",[208],{"type":29,"value":209},"注意点1：COOP/COEP ヘッダーが必要",{"type":23,"tag":31,"props":211,"children":212},{},[213,215,225],{"type":29,"value":214},"ブラウザの ",{"type":23,"tag":42,"props":216,"children":218},{"content":217},"複数のスレッド間でメモリを直接共有できるJavaScriptオブジェクト。Web Workerとのデータ共有に使われる",[219],{"type":23,"tag":47,"props":220,"children":222},{"className":221},[],[223],{"type":29,"value":224},"SharedArrayBuffer",{"type":29,"value":226}," を使用するため、COOP/COEP ヘッダーが必要となります。\nこのヘッダの指定方法はデプロイ先によって変わるため、本記事では設定方法を割愛します。",{"type":23,"tag":228,"props":229,"children":231},"h4",{"id":230},"マルチスレッド環境下でサイドチャネル攻撃を防ぐには",[232],{"type":29,"value":230},{"type":23,"tag":31,"props":234,"children":235},{},[236,238,243],{"type":29,"value":237},"マルチスレッドプログラミングを実現するには、スレッド間でメモリを共有する必要があります。それを実現する具体的な機能が、",{"type":23,"tag":47,"props":239,"children":241},{"className":240},[],[242],{"type":29,"value":224},{"type":29,"value":244},"というものです。非常に便利な機能なのですが、バックグラウンドでタイマーを実行できるようになるので高精度なタイマーが利用できるようになります。これを悪用し、CPUキャッシュアクセス時間などからデータを推測し窃取するという、サイドチャネル攻撃が可能になるという弱点があります。",{"type":23,"tag":31,"props":246,"children":247},{},[248],{"type":29,"value":249},"この問題を解決するためには、クロスオリジン分離という仕組みが必要になります。",{"type":23,"tag":251,"props":252,"children":253},"table",{},[254,278],{"type":23,"tag":255,"props":256,"children":257},"thead",{},[258],{"type":23,"tag":259,"props":260,"children":261},"tr",{},[262,268,273],{"type":23,"tag":263,"props":264,"children":265},"th",{},[266],{"type":29,"value":267},"ヘッダ",{"type":23,"tag":263,"props":269,"children":270},{},[271],{"type":29,"value":272},"正式名称",{"type":23,"tag":263,"props":274,"children":275},{},[276],{"type":29,"value":277},"役割",{"type":23,"tag":279,"props":280,"children":281},"tbody",{},[282,305],{"type":23,"tag":259,"props":283,"children":284},{},[285,291,300],{"type":23,"tag":286,"props":287,"children":288},"td",{},[289],{"type":29,"value":290},"COOP",{"type":23,"tag":286,"props":292,"children":293},{},[294],{"type":23,"tag":47,"props":295,"children":297},{"className":296},[],[298],{"type":29,"value":299},"Cross-Origin-Opener-Policy",{"type":23,"tag":286,"props":301,"children":302},{},[303],{"type":29,"value":304},"他オリジンのページと同じブラウザプロセスを共有しなくする",{"type":23,"tag":259,"props":306,"children":307},{},[308,313,322],{"type":23,"tag":286,"props":309,"children":310},{},[311],{"type":29,"value":312},"COEP",{"type":23,"tag":286,"props":314,"children":315},{},[316],{"type":23,"tag":47,"props":317,"children":319},{"className":318},[],[320],{"type":29,"value":321},"Cross-Origin-Embedder-Policy",{"type":23,"tag":286,"props":323,"children":324},{},[325],{"type":29,"value":326},"他オリジンのリソースを読み込む際にCORSやCORPヘッダを必須にする",{"type":23,"tag":31,"props":328,"children":329},{},[330],{"type":29,"value":331},"この２つの仕組みを組み合わせることで、「他オリジンのプロセスと物理的に遮断した上で、かつリソースの読み込みに制限をかける」ことができるので、仮に高精度なタイマーを使って攻撃しようとしても、そもそも他オリジンの情報を盗むことが不可能になるという仕組みです。",{"type":23,"tag":204,"props":333,"children":335},{"id":334},"注意点2ワークロードのインストールが必要",[336],{"type":29,"value":337},"注意点2：ワークロードのインストールが必要",{"type":23,"tag":31,"props":339,"children":340},{},[341],{"type":29,"value":342},"デフォルトでは有効になっていないため、以下のコマンドでワークロードのインストールが必要です。",{"type":23,"tag":140,"props":344,"children":348},{"className":345,"code":346,"language":347,"meta":7,"style":7},"language-bash shiki shiki-themes vitesse-dark","dotnet workload install wasm-tools\n","bash",[349],{"type":23,"tag":47,"props":350,"children":351},{"__ignoreMap":7},[352],{"type":23,"tag":150,"props":353,"children":354},{"class":152,"line":17},[355,361,367,372],{"type":23,"tag":150,"props":356,"children":358},{"style":357},"--shiki-default:#80A665",[359],{"type":29,"value":360},"dotnet",{"type":23,"tag":150,"props":362,"children":364},{"style":363},"--shiki-default:#C98A7D",[365],{"type":29,"value":366}," workload",{"type":23,"tag":150,"props":368,"children":369},{"style":363},[370],{"type":29,"value":371}," install",{"type":23,"tag":150,"props":373,"children":374},{"style":363},[375],{"type":29,"value":376}," wasm-tools\n",{"type":23,"tag":228,"props":378,"children":380},{"id":379},"開発用サーバープロジェクトの追加",[381],{"type":29,"value":379},{"type":23,"tag":31,"props":383,"children":384},{},[385],{"type":29,"value":386},"Blazor WASM の DevServer は COOP/COEP ヘッダーを自動付与しないため、ASP.NET Core のホストプロジェクトを新規作成しました。そういう設定もありそうなのですが、一旦これでいいかということで妥協しています。",{"type":23,"tag":31,"props":388,"children":389},{},[390],{"type":29,"value":391},"実装例を示します。",{"type":23,"tag":140,"props":393,"children":397},{"className":394,"code":395,"language":396,"meta":7,"style":7},"language-csharp shiki shiki-themes vitesse-dark","// Program.cs\nvar builder = WebApplication.CreateBuilder(args);\nvar app = builder.Build();\n\napp.Use(async (context, next) =>\n{\n    context.Response.Headers[\"Cross-Origin-Opener-Policy\"] = \"same-origin\";\n    context.Response.Headers[\"Cross-Origin-Embedder-Policy\"] = \"require-corp\";\n    await next();\n});\n\napp.UseBlazorFrameworkFiles();\napp.UseStaticFiles();\napp.MapFallbackToFile(\"index.html\");\n\napp.Run();\n","csharp",[398],{"type":23,"tag":47,"props":399,"children":400},{"__ignoreMap":7},[401,410,461,496,506,563,572,646,711,728,737,745,766,787,825,833],{"type":23,"tag":150,"props":402,"children":403},{"class":152,"line":17},[404],{"type":23,"tag":150,"props":405,"children":407},{"style":406},"--shiki-default:#758575DD",[408],{"type":29,"value":409},"// Program.cs\n",{"type":23,"tag":150,"props":411,"children":413},{"class":152,"line":412},2,[414,420,425,430,436,441,446,451,456],{"type":23,"tag":150,"props":415,"children":417},{"style":416},"--shiki-default:#CB7676",[418],{"type":29,"value":419},"var",{"type":23,"tag":150,"props":421,"children":422},{"style":357},[423],{"type":29,"value":424}," builder",{"type":23,"tag":150,"props":426,"children":427},{"style":156},[428],{"type":29,"value":429}," =",{"type":23,"tag":150,"props":431,"children":433},{"style":432},"--shiki-default:#BD976A",[434],{"type":29,"value":435}," WebApplication",{"type":23,"tag":150,"props":437,"children":438},{"style":156},[439],{"type":29,"value":440},".",{"type":23,"tag":150,"props":442,"children":443},{"style":357},[444],{"type":29,"value":445},"CreateBuilder",{"type":23,"tag":150,"props":447,"children":448},{"style":156},[449],{"type":29,"value":450},"(",{"type":23,"tag":150,"props":452,"children":453},{"style":432},[454],{"type":29,"value":455},"args",{"type":23,"tag":150,"props":457,"children":458},{"style":156},[459],{"type":29,"value":460},");\n",{"type":23,"tag":150,"props":462,"children":464},{"class":152,"line":463},3,[465,469,474,478,482,486,491],{"type":23,"tag":150,"props":466,"children":467},{"style":416},[468],{"type":29,"value":419},{"type":23,"tag":150,"props":470,"children":471},{"style":357},[472],{"type":29,"value":473}," app",{"type":23,"tag":150,"props":475,"children":476},{"style":156},[477],{"type":29,"value":429},{"type":23,"tag":150,"props":479,"children":480},{"style":432},[481],{"type":29,"value":424},{"type":23,"tag":150,"props":483,"children":484},{"style":156},[485],{"type":29,"value":440},{"type":23,"tag":150,"props":487,"children":488},{"style":357},[489],{"type":29,"value":490},"Build",{"type":23,"tag":150,"props":492,"children":493},{"style":156},[494],{"type":29,"value":495},"();\n",{"type":23,"tag":150,"props":497,"children":499},{"class":152,"line":498},4,[500],{"type":23,"tag":150,"props":501,"children":503},{"emptyLinePlaceholder":502},true,[504],{"type":29,"value":505},"\n",{"type":23,"tag":150,"props":507,"children":509},{"class":152,"line":508},5,[510,515,519,524,528,533,538,543,548,553,558],{"type":23,"tag":150,"props":511,"children":512},{"style":432},[513],{"type":29,"value":514},"app",{"type":23,"tag":150,"props":516,"children":517},{"style":156},[518],{"type":29,"value":440},{"type":23,"tag":150,"props":520,"children":521},{"style":357},[522],{"type":29,"value":523},"Use",{"type":23,"tag":150,"props":525,"children":526},{"style":156},[527],{"type":29,"value":450},{"type":23,"tag":150,"props":529,"children":530},{"style":416},[531],{"type":29,"value":532},"async",{"type":23,"tag":150,"props":534,"children":535},{"style":156},[536],{"type":29,"value":537}," (",{"type":23,"tag":150,"props":539,"children":540},{"style":357},[541],{"type":29,"value":542},"context",{"type":23,"tag":150,"props":544,"children":545},{"style":156},[546],{"type":29,"value":547},",",{"type":23,"tag":150,"props":549,"children":550},{"style":357},[551],{"type":29,"value":552}," next",{"type":23,"tag":150,"props":554,"children":555},{"style":156},[556],{"type":29,"value":557},")",{"type":23,"tag":150,"props":559,"children":560},{"style":416},[561],{"type":29,"value":562}," =>\n",{"type":23,"tag":150,"props":564,"children":566},{"class":152,"line":565},6,[567],{"type":23,"tag":150,"props":568,"children":569},{"style":156},[570],{"type":29,"value":571},"{\n",{"type":23,"tag":150,"props":573,"children":575},{"class":152,"line":574},7,[576,581,585,590,594,599,604,610,614,618,623,627,632,637,641],{"type":23,"tag":150,"props":577,"children":578},{"style":432},[579],{"type":29,"value":580},"    context",{"type":23,"tag":150,"props":582,"children":583},{"style":156},[584],{"type":29,"value":440},{"type":23,"tag":150,"props":586,"children":587},{"style":432},[588],{"type":29,"value":589},"Response",{"type":23,"tag":150,"props":591,"children":592},{"style":156},[593],{"type":29,"value":440},{"type":23,"tag":150,"props":595,"children":596},{"style":432},[597],{"type":29,"value":598},"Headers",{"type":23,"tag":150,"props":600,"children":601},{"style":156},[602],{"type":29,"value":603},"[",{"type":23,"tag":150,"props":605,"children":607},{"style":606},"--shiki-default:#C98A7D77",[608],{"type":29,"value":609},"\"",{"type":23,"tag":150,"props":611,"children":612},{"style":363},[613],{"type":29,"value":299},{"type":23,"tag":150,"props":615,"children":616},{"style":606},[617],{"type":29,"value":609},{"type":23,"tag":150,"props":619,"children":620},{"style":156},[621],{"type":29,"value":622},"]",{"type":23,"tag":150,"props":624,"children":625},{"style":156},[626],{"type":29,"value":429},{"type":23,"tag":150,"props":628,"children":629},{"style":606},[630],{"type":29,"value":631}," \"",{"type":23,"tag":150,"props":633,"children":634},{"style":363},[635],{"type":29,"value":636},"same-origin",{"type":23,"tag":150,"props":638,"children":639},{"style":606},[640],{"type":29,"value":609},{"type":23,"tag":150,"props":642,"children":643},{"style":156},[644],{"type":29,"value":645},";\n",{"type":23,"tag":150,"props":647,"children":649},{"class":152,"line":648},8,[650,654,658,662,666,670,674,678,682,686,690,694,698,703,707],{"type":23,"tag":150,"props":651,"children":652},{"style":432},[653],{"type":29,"value":580},{"type":23,"tag":150,"props":655,"children":656},{"style":156},[657],{"type":29,"value":440},{"type":23,"tag":150,"props":659,"children":660},{"style":432},[661],{"type":29,"value":589},{"type":23,"tag":150,"props":663,"children":664},{"style":156},[665],{"type":29,"value":440},{"type":23,"tag":150,"props":667,"children":668},{"style":432},[669],{"type":29,"value":598},{"type":23,"tag":150,"props":671,"children":672},{"style":156},[673],{"type":29,"value":603},{"type":23,"tag":150,"props":675,"children":676},{"style":606},[677],{"type":29,"value":609},{"type":23,"tag":150,"props":679,"children":680},{"style":363},[681],{"type":29,"value":321},{"type":23,"tag":150,"props":683,"children":684},{"style":606},[685],{"type":29,"value":609},{"type":23,"tag":150,"props":687,"children":688},{"style":156},[689],{"type":29,"value":622},{"type":23,"tag":150,"props":691,"children":692},{"style":156},[693],{"type":29,"value":429},{"type":23,"tag":150,"props":695,"children":696},{"style":606},[697],{"type":29,"value":631},{"type":23,"tag":150,"props":699,"children":700},{"style":363},[701],{"type":29,"value":702},"require-corp",{"type":23,"tag":150,"props":704,"children":705},{"style":606},[706],{"type":29,"value":609},{"type":23,"tag":150,"props":708,"children":709},{"style":156},[710],{"type":29,"value":645},{"type":23,"tag":150,"props":712,"children":714},{"class":152,"line":713},9,[715,720,724],{"type":23,"tag":150,"props":716,"children":717},{"style":416},[718],{"type":29,"value":719},"    await",{"type":23,"tag":150,"props":721,"children":722},{"style":357},[723],{"type":29,"value":552},{"type":23,"tag":150,"props":725,"children":726},{"style":156},[727],{"type":29,"value":495},{"type":23,"tag":150,"props":729,"children":731},{"class":152,"line":730},10,[732],{"type":23,"tag":150,"props":733,"children":734},{"style":156},[735],{"type":29,"value":736},"});\n",{"type":23,"tag":150,"props":738,"children":740},{"class":152,"line":739},11,[741],{"type":23,"tag":150,"props":742,"children":743},{"emptyLinePlaceholder":502},[744],{"type":29,"value":505},{"type":23,"tag":150,"props":746,"children":748},{"class":152,"line":747},12,[749,753,757,762],{"type":23,"tag":150,"props":750,"children":751},{"style":432},[752],{"type":29,"value":514},{"type":23,"tag":150,"props":754,"children":755},{"style":156},[756],{"type":29,"value":440},{"type":23,"tag":150,"props":758,"children":759},{"style":357},[760],{"type":29,"value":761},"UseBlazorFrameworkFiles",{"type":23,"tag":150,"props":763,"children":764},{"style":156},[765],{"type":29,"value":495},{"type":23,"tag":150,"props":767,"children":769},{"class":152,"line":768},13,[770,774,778,783],{"type":23,"tag":150,"props":771,"children":772},{"style":432},[773],{"type":29,"value":514},{"type":23,"tag":150,"props":775,"children":776},{"style":156},[777],{"type":29,"value":440},{"type":23,"tag":150,"props":779,"children":780},{"style":357},[781],{"type":29,"value":782},"UseStaticFiles",{"type":23,"tag":150,"props":784,"children":785},{"style":156},[786],{"type":29,"value":495},{"type":23,"tag":150,"props":788,"children":790},{"class":152,"line":789},14,[791,795,799,804,808,812,817,821],{"type":23,"tag":150,"props":792,"children":793},{"style":432},[794],{"type":29,"value":514},{"type":23,"tag":150,"props":796,"children":797},{"style":156},[798],{"type":29,"value":440},{"type":23,"tag":150,"props":800,"children":801},{"style":357},[802],{"type":29,"value":803},"MapFallbackToFile",{"type":23,"tag":150,"props":805,"children":806},{"style":156},[807],{"type":29,"value":450},{"type":23,"tag":150,"props":809,"children":810},{"style":606},[811],{"type":29,"value":609},{"type":23,"tag":150,"props":813,"children":814},{"style":363},[815],{"type":29,"value":816},"index.html",{"type":23,"tag":150,"props":818,"children":819},{"style":606},[820],{"type":29,"value":609},{"type":23,"tag":150,"props":822,"children":823},{"style":156},[824],{"type":29,"value":460},{"type":23,"tag":150,"props":826,"children":828},{"class":152,"line":827},15,[829],{"type":23,"tag":150,"props":830,"children":831},{"emptyLinePlaceholder":502},[832],{"type":29,"value":505},{"type":23,"tag":150,"props":834,"children":836},{"class":152,"line":835},16,[837,841,845,850],{"type":23,"tag":150,"props":838,"children":839},{"style":432},[840],{"type":29,"value":514},{"type":23,"tag":150,"props":842,"children":843},{"style":156},[844],{"type":29,"value":440},{"type":23,"tag":150,"props":846,"children":847},{"style":357},[848],{"type":29,"value":849},"Run",{"type":23,"tag":150,"props":851,"children":852},{"style":156},[853],{"type":29,"value":495},{"type":23,"tag":31,"props":855,"children":856},{},[857],{"type":29,"value":858},"肝となる部分はレスポンスヘッダを設定している処理ですね。ここで、クロスオリジン分離を宣言しています。",{"type":23,"tag":228,"props":860,"children":862},{"id":861},"音楽生成のバックグラウンドスレッド化",[863],{"type":29,"value":861},{"type":23,"tag":31,"props":865,"children":866},{},[867,869,875],{"type":29,"value":868},"バックグラウンドスレッドで実行すること自体は、普段通り",{"type":23,"tag":47,"props":870,"children":872},{"className":871},[],[873],{"type":29,"value":874},"async/await",{"type":29,"value":876},"で実装することで実現できます。したがって、ここまで設定できれば普段通り非同期処理を実装するだけとなります。",{"type":23,"tag":228,"props":878,"children":880},{"id":879},"再生処理ではchanneltを利用する",[881,883,888],{"type":29,"value":882},"再生処理では",{"type":23,"tag":47,"props":884,"children":886},{"className":885},[],[887],{"type":29,"value":76},{"type":29,"value":889},"を利用する",{"type":23,"tag":31,"props":891,"children":892},{},[893,895,905,907,913],{"type":29,"value":894},"肝心の再生処理です。",{"type":23,"tag":42,"props":896,"children":898},{"content":897},".NET標準のスレッド間データ受け渡し用コレクション。非同期でProducerとConsumerを接続できる",[899],{"type":23,"tag":47,"props":900,"children":902},{"className":901},[],[903],{"type":29,"value":904},"Channel",{"type":29,"value":906},"\nを使った\n",{"type":23,"tag":42,"props":908,"children":910},{"content":909},"データを生成する側（Producer）と消費する側（Consumer）を分離し、非同期にデータを受け渡す設計パターン",[911],{"type":29,"value":912},"Producer-Consumer パターン",{"type":29,"value":914},"\nで、バックグラウンド生成しつつストリーミング再生を維持するということをしました。",{"type":23,"tag":916,"props":917,"children":919},"h5",{"id":918},"そもそもなぜストリーミングを使っているか",[920],{"type":29,"value":921},"そもそもなぜストリーミングを使っているか？",{"type":23,"tag":31,"props":923,"children":924},{},[925],{"type":29,"value":926},"単純な話、楽曲の音声を全て生成してから再生しようとすると、再生が始まるまで時間がかかるからです。\n少し生成→再生→さらに生成→再生というのをスレッドを分けて実現しようというのが主旨となります。",{"type":23,"tag":916,"props":928,"children":930},{"id":929},"producer側の実装",[931],{"type":29,"value":932},"Producer側の実装",{"type":23,"tag":140,"props":934,"children":936},{"className":394,"code":935,"language":396,"meta":7,"style":7},"public (ChannelReader\u003Cbyte[]> Reader, Task GenerationTask) GetSoundsForPlayStreaming(\n    int seekIndex, CancellationToken cancellationToken = default)\n{\n    // UIスレッドでスナップショット取得\n    var tempo = EditingMusic.Value.Settings.Tempo;\n    var trackSnapshots = EditingMusic.Value.Tracks.Select(t => t.Clone()).ToList();\n\n    var channel = Channel.CreateBounded\u003Cbyte[]>(new BoundedChannelOptions(2)\n    {\n        FullMode = BoundedChannelFullMode.Wait\n    });\n\n    // (Producer)バックグラウンドで音声をストリーミング生成する\n    var task = Task.Run(async () =>\n    {\n        try\n        {\n            foreach (var buffer in SoundGenerator.GetSoundsForPlay(seekIndex, tempo, trackSnapshots))\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n                // ここでチャンネルに書き込む Producer -> Consumer\n                await channel.Writer.WriteAsync(buffer, cancellationToken);\n            }\n        }\n        finally\n        {\n            channel.Writer.Complete();\n        }\n    }, cancellationToken);\n\n    return (channel.Reader, task);\n}\n",[937],{"type":23,"tag":47,"props":938,"children":939},{"__ignoreMap":7},[940,1005,1046,1053,1061,1114,1202,1209,1276,1284,1310,1318,1325,1333,1378,1385,1393,1402,1473,1482,1504,1513,1565,1574,1583,1592,1600,1630,1638,1655,1663,1702],{"type":23,"tag":150,"props":941,"children":942},{"class":152,"line":17},[943,948,952,958,962,967,972,977,981,986,991,995,1000],{"type":23,"tag":150,"props":944,"children":945},{"style":416},[946],{"type":29,"value":947},"public",{"type":23,"tag":150,"props":949,"children":950},{"style":156},[951],{"type":29,"value":537},{"type":23,"tag":150,"props":953,"children":955},{"style":954},"--shiki-default:#5DA994",[956],{"type":29,"value":957},"ChannelReader",{"type":23,"tag":150,"props":959,"children":960},{"style":156},[961],{"type":29,"value":159},{"type":23,"tag":150,"props":963,"children":964},{"style":162},[965],{"type":29,"value":966},"byte",{"type":23,"tag":150,"props":968,"children":969},{"style":156},[970],{"type":29,"value":971},"[]>",{"type":23,"tag":150,"props":973,"children":974},{"style":357},[975],{"type":29,"value":976}," Reader",{"type":23,"tag":150,"props":978,"children":979},{"style":156},[980],{"type":29,"value":547},{"type":23,"tag":150,"props":982,"children":983},{"style":954},[984],{"type":29,"value":985}," Task",{"type":23,"tag":150,"props":987,"children":988},{"style":357},[989],{"type":29,"value":990}," GenerationTask",{"type":23,"tag":150,"props":992,"children":993},{"style":156},[994],{"type":29,"value":557},{"type":23,"tag":150,"props":996,"children":997},{"style":357},[998],{"type":29,"value":999}," GetSoundsForPlayStreaming",{"type":23,"tag":150,"props":1001,"children":1002},{"style":156},[1003],{"type":29,"value":1004},"(\n",{"type":23,"tag":150,"props":1006,"children":1007},{"class":152,"line":412},[1008,1013,1018,1022,1027,1032,1036,1041],{"type":23,"tag":150,"props":1009,"children":1010},{"style":162},[1011],{"type":29,"value":1012},"    int",{"type":23,"tag":150,"props":1014,"children":1015},{"style":357},[1016],{"type":29,"value":1017}," seekIndex",{"type":23,"tag":150,"props":1019,"children":1020},{"style":156},[1021],{"type":29,"value":547},{"type":23,"tag":150,"props":1023,"children":1024},{"style":954},[1025],{"type":29,"value":1026}," CancellationToken",{"type":23,"tag":150,"props":1028,"children":1029},{"style":357},[1030],{"type":29,"value":1031}," cancellationToken",{"type":23,"tag":150,"props":1033,"children":1034},{"style":156},[1035],{"type":29,"value":429},{"type":23,"tag":150,"props":1037,"children":1038},{"style":416},[1039],{"type":29,"value":1040}," default",{"type":23,"tag":150,"props":1042,"children":1043},{"style":156},[1044],{"type":29,"value":1045},")\n",{"type":23,"tag":150,"props":1047,"children":1048},{"class":152,"line":463},[1049],{"type":23,"tag":150,"props":1050,"children":1051},{"style":156},[1052],{"type":29,"value":571},{"type":23,"tag":150,"props":1054,"children":1055},{"class":152,"line":498},[1056],{"type":23,"tag":150,"props":1057,"children":1058},{"style":406},[1059],{"type":29,"value":1060},"    // UIスレッドでスナップショット取得\n",{"type":23,"tag":150,"props":1062,"children":1063},{"class":152,"line":508},[1064,1069,1074,1078,1083,1087,1092,1096,1101,1105,1110],{"type":23,"tag":150,"props":1065,"children":1066},{"style":416},[1067],{"type":29,"value":1068},"    var",{"type":23,"tag":150,"props":1070,"children":1071},{"style":357},[1072],{"type":29,"value":1073}," tempo",{"type":23,"tag":150,"props":1075,"children":1076},{"style":156},[1077],{"type":29,"value":429},{"type":23,"tag":150,"props":1079,"children":1080},{"style":432},[1081],{"type":29,"value":1082}," EditingMusic",{"type":23,"tag":150,"props":1084,"children":1085},{"style":156},[1086],{"type":29,"value":440},{"type":23,"tag":150,"props":1088,"children":1089},{"style":432},[1090],{"type":29,"value":1091},"Value",{"type":23,"tag":150,"props":1093,"children":1094},{"style":156},[1095],{"type":29,"value":440},{"type":23,"tag":150,"props":1097,"children":1098},{"style":432},[1099],{"type":29,"value":1100},"Settings",{"type":23,"tag":150,"props":1102,"children":1103},{"style":156},[1104],{"type":29,"value":440},{"type":23,"tag":150,"props":1106,"children":1107},{"style":432},[1108],{"type":29,"value":1109},"Tempo",{"type":23,"tag":150,"props":1111,"children":1112},{"style":156},[1113],{"type":29,"value":645},{"type":23,"tag":150,"props":1115,"children":1116},{"class":152,"line":565},[1117,1121,1126,1130,1134,1138,1142,1146,1151,1155,1160,1164,1169,1174,1179,1183,1188,1193,1198],{"type":23,"tag":150,"props":1118,"children":1119},{"style":416},[1120],{"type":29,"value":1068},{"type":23,"tag":150,"props":1122,"children":1123},{"style":357},[1124],{"type":29,"value":1125}," trackSnapshots",{"type":23,"tag":150,"props":1127,"children":1128},{"style":156},[1129],{"type":29,"value":429},{"type":23,"tag":150,"props":1131,"children":1132},{"style":432},[1133],{"type":29,"value":1082},{"type":23,"tag":150,"props":1135,"children":1136},{"style":156},[1137],{"type":29,"value":440},{"type":23,"tag":150,"props":1139,"children":1140},{"style":432},[1141],{"type":29,"value":1091},{"type":23,"tag":150,"props":1143,"children":1144},{"style":156},[1145],{"type":29,"value":440},{"type":23,"tag":150,"props":1147,"children":1148},{"style":432},[1149],{"type":29,"value":1150},"Tracks",{"type":23,"tag":150,"props":1152,"children":1153},{"style":156},[1154],{"type":29,"value":440},{"type":23,"tag":150,"props":1156,"children":1157},{"style":357},[1158],{"type":29,"value":1159},"Select",{"type":23,"tag":150,"props":1161,"children":1162},{"style":156},[1163],{"type":29,"value":450},{"type":23,"tag":150,"props":1165,"children":1166},{"style":357},[1167],{"type":29,"value":1168},"t",{"type":23,"tag":150,"props":1170,"children":1171},{"style":416},[1172],{"type":29,"value":1173}," =>",{"type":23,"tag":150,"props":1175,"children":1176},{"style":432},[1177],{"type":29,"value":1178}," t",{"type":23,"tag":150,"props":1180,"children":1181},{"style":156},[1182],{"type":29,"value":440},{"type":23,"tag":150,"props":1184,"children":1185},{"style":357},[1186],{"type":29,"value":1187},"Clone",{"type":23,"tag":150,"props":1189,"children":1190},{"style":156},[1191],{"type":29,"value":1192},"()).",{"type":23,"tag":150,"props":1194,"children":1195},{"style":357},[1196],{"type":29,"value":1197},"ToList",{"type":23,"tag":150,"props":1199,"children":1200},{"style":156},[1201],{"type":29,"value":495},{"type":23,"tag":150,"props":1203,"children":1204},{"class":152,"line":574},[1205],{"type":23,"tag":150,"props":1206,"children":1207},{"emptyLinePlaceholder":502},[1208],{"type":29,"value":505},{"type":23,"tag":150,"props":1210,"children":1211},{"class":152,"line":648},[1212,1216,1221,1225,1230,1234,1239,1243,1247,1252,1257,1262,1266,1272],{"type":23,"tag":150,"props":1213,"children":1214},{"style":416},[1215],{"type":29,"value":1068},{"type":23,"tag":150,"props":1217,"children":1218},{"style":357},[1219],{"type":29,"value":1220}," channel",{"type":23,"tag":150,"props":1222,"children":1223},{"style":156},[1224],{"type":29,"value":429},{"type":23,"tag":150,"props":1226,"children":1227},{"style":432},[1228],{"type":29,"value":1229}," Channel",{"type":23,"tag":150,"props":1231,"children":1232},{"style":156},[1233],{"type":29,"value":440},{"type":23,"tag":150,"props":1235,"children":1236},{"style":357},[1237],{"type":29,"value":1238},"CreateBounded",{"type":23,"tag":150,"props":1240,"children":1241},{"style":156},[1242],{"type":29,"value":159},{"type":23,"tag":150,"props":1244,"children":1245},{"style":162},[1246],{"type":29,"value":966},{"type":23,"tag":150,"props":1248,"children":1249},{"style":156},[1250],{"type":29,"value":1251},"[]>(",{"type":23,"tag":150,"props":1253,"children":1254},{"style":416},[1255],{"type":29,"value":1256},"new",{"type":23,"tag":150,"props":1258,"children":1259},{"style":954},[1260],{"type":29,"value":1261}," BoundedChannelOptions",{"type":23,"tag":150,"props":1263,"children":1264},{"style":156},[1265],{"type":29,"value":450},{"type":23,"tag":150,"props":1267,"children":1269},{"style":1268},"--shiki-default:#4C9A91",[1270],{"type":29,"value":1271},"2",{"type":23,"tag":150,"props":1273,"children":1274},{"style":156},[1275],{"type":29,"value":1045},{"type":23,"tag":150,"props":1277,"children":1278},{"class":152,"line":713},[1279],{"type":23,"tag":150,"props":1280,"children":1281},{"style":156},[1282],{"type":29,"value":1283},"    {\n",{"type":23,"tag":150,"props":1285,"children":1286},{"class":152,"line":730},[1287,1292,1296,1301,1305],{"type":23,"tag":150,"props":1288,"children":1289},{"style":432},[1290],{"type":29,"value":1291},"        FullMode",{"type":23,"tag":150,"props":1293,"children":1294},{"style":156},[1295],{"type":29,"value":429},{"type":23,"tag":150,"props":1297,"children":1298},{"style":432},[1299],{"type":29,"value":1300}," BoundedChannelFullMode",{"type":23,"tag":150,"props":1302,"children":1303},{"style":156},[1304],{"type":29,"value":440},{"type":23,"tag":150,"props":1306,"children":1307},{"style":432},[1308],{"type":29,"value":1309},"Wait\n",{"type":23,"tag":150,"props":1311,"children":1312},{"class":152,"line":739},[1313],{"type":23,"tag":150,"props":1314,"children":1315},{"style":156},[1316],{"type":29,"value":1317},"    });\n",{"type":23,"tag":150,"props":1319,"children":1320},{"class":152,"line":747},[1321],{"type":23,"tag":150,"props":1322,"children":1323},{"emptyLinePlaceholder":502},[1324],{"type":29,"value":505},{"type":23,"tag":150,"props":1326,"children":1327},{"class":152,"line":768},[1328],{"type":23,"tag":150,"props":1329,"children":1330},{"style":406},[1331],{"type":29,"value":1332},"    // (Producer)バックグラウンドで音声をストリーミング生成する\n",{"type":23,"tag":150,"props":1334,"children":1335},{"class":152,"line":789},[1336,1340,1345,1349,1353,1357,1361,1365,1369,1374],{"type":23,"tag":150,"props":1337,"children":1338},{"style":416},[1339],{"type":29,"value":1068},{"type":23,"tag":150,"props":1341,"children":1342},{"style":357},[1343],{"type":29,"value":1344}," task",{"type":23,"tag":150,"props":1346,"children":1347},{"style":156},[1348],{"type":29,"value":429},{"type":23,"tag":150,"props":1350,"children":1351},{"style":432},[1352],{"type":29,"value":985},{"type":23,"tag":150,"props":1354,"children":1355},{"style":156},[1356],{"type":29,"value":440},{"type":23,"tag":150,"props":1358,"children":1359},{"style":357},[1360],{"type":29,"value":849},{"type":23,"tag":150,"props":1362,"children":1363},{"style":156},[1364],{"type":29,"value":450},{"type":23,"tag":150,"props":1366,"children":1367},{"style":416},[1368],{"type":29,"value":532},{"type":23,"tag":150,"props":1370,"children":1371},{"style":156},[1372],{"type":29,"value":1373}," ()",{"type":23,"tag":150,"props":1375,"children":1376},{"style":416},[1377],{"type":29,"value":562},{"type":23,"tag":150,"props":1379,"children":1380},{"class":152,"line":827},[1381],{"type":23,"tag":150,"props":1382,"children":1383},{"style":156},[1384],{"type":29,"value":1283},{"type":23,"tag":150,"props":1386,"children":1387},{"class":152,"line":835},[1388],{"type":23,"tag":150,"props":1389,"children":1390},{"style":162},[1391],{"type":29,"value":1392},"        try\n",{"type":23,"tag":150,"props":1394,"children":1396},{"class":152,"line":1395},17,[1397],{"type":23,"tag":150,"props":1398,"children":1399},{"style":156},[1400],{"type":29,"value":1401},"        {\n",{"type":23,"tag":150,"props":1403,"children":1405},{"class":152,"line":1404},18,[1406,1411,1415,1419,1424,1429,1434,1438,1443,1447,1452,1456,1460,1464,1468],{"type":23,"tag":150,"props":1407,"children":1408},{"style":162},[1409],{"type":29,"value":1410},"            foreach",{"type":23,"tag":150,"props":1412,"children":1413},{"style":156},[1414],{"type":29,"value":537},{"type":23,"tag":150,"props":1416,"children":1417},{"style":416},[1418],{"type":29,"value":419},{"type":23,"tag":150,"props":1420,"children":1421},{"style":357},[1422],{"type":29,"value":1423}," buffer",{"type":23,"tag":150,"props":1425,"children":1426},{"style":162},[1427],{"type":29,"value":1428}," in",{"type":23,"tag":150,"props":1430,"children":1431},{"style":432},[1432],{"type":29,"value":1433}," SoundGenerator",{"type":23,"tag":150,"props":1435,"children":1436},{"style":156},[1437],{"type":29,"value":440},{"type":23,"tag":150,"props":1439,"children":1440},{"style":357},[1441],{"type":29,"value":1442},"GetSoundsForPlay",{"type":23,"tag":150,"props":1444,"children":1445},{"style":156},[1446],{"type":29,"value":450},{"type":23,"tag":150,"props":1448,"children":1449},{"style":432},[1450],{"type":29,"value":1451},"seekIndex",{"type":23,"tag":150,"props":1453,"children":1454},{"style":156},[1455],{"type":29,"value":547},{"type":23,"tag":150,"props":1457,"children":1458},{"style":432},[1459],{"type":29,"value":1073},{"type":23,"tag":150,"props":1461,"children":1462},{"style":156},[1463],{"type":29,"value":547},{"type":23,"tag":150,"props":1465,"children":1466},{"style":432},[1467],{"type":29,"value":1125},{"type":23,"tag":150,"props":1469,"children":1470},{"style":156},[1471],{"type":29,"value":1472},"))\n",{"type":23,"tag":150,"props":1474,"children":1476},{"class":152,"line":1475},19,[1477],{"type":23,"tag":150,"props":1478,"children":1479},{"style":156},[1480],{"type":29,"value":1481},"            {\n",{"type":23,"tag":150,"props":1483,"children":1485},{"class":152,"line":1484},20,[1486,1491,1495,1500],{"type":23,"tag":150,"props":1487,"children":1488},{"style":432},[1489],{"type":29,"value":1490},"                cancellationToken",{"type":23,"tag":150,"props":1492,"children":1493},{"style":156},[1494],{"type":29,"value":440},{"type":23,"tag":150,"props":1496,"children":1497},{"style":357},[1498],{"type":29,"value":1499},"ThrowIfCancellationRequested",{"type":23,"tag":150,"props":1501,"children":1502},{"style":156},[1503],{"type":29,"value":495},{"type":23,"tag":150,"props":1505,"children":1507},{"class":152,"line":1506},21,[1508],{"type":23,"tag":150,"props":1509,"children":1510},{"style":406},[1511],{"type":29,"value":1512},"                // ここでチャンネルに書き込む Producer -> Consumer\n",{"type":23,"tag":150,"props":1514,"children":1516},{"class":152,"line":1515},22,[1517,1522,1526,1530,1535,1539,1544,1548,1553,1557,1561],{"type":23,"tag":150,"props":1518,"children":1519},{"style":416},[1520],{"type":29,"value":1521},"                await",{"type":23,"tag":150,"props":1523,"children":1524},{"style":432},[1525],{"type":29,"value":1220},{"type":23,"tag":150,"props":1527,"children":1528},{"style":156},[1529],{"type":29,"value":440},{"type":23,"tag":150,"props":1531,"children":1532},{"style":432},[1533],{"type":29,"value":1534},"Writer",{"type":23,"tag":150,"props":1536,"children":1537},{"style":156},[1538],{"type":29,"value":440},{"type":23,"tag":150,"props":1540,"children":1541},{"style":357},[1542],{"type":29,"value":1543},"WriteAsync",{"type":23,"tag":150,"props":1545,"children":1546},{"style":156},[1547],{"type":29,"value":450},{"type":23,"tag":150,"props":1549,"children":1550},{"style":432},[1551],{"type":29,"value":1552},"buffer",{"type":23,"tag":150,"props":1554,"children":1555},{"style":156},[1556],{"type":29,"value":547},{"type":23,"tag":150,"props":1558,"children":1559},{"style":432},[1560],{"type":29,"value":1031},{"type":23,"tag":150,"props":1562,"children":1563},{"style":156},[1564],{"type":29,"value":460},{"type":23,"tag":150,"props":1566,"children":1568},{"class":152,"line":1567},23,[1569],{"type":23,"tag":150,"props":1570,"children":1571},{"style":156},[1572],{"type":29,"value":1573},"            }\n",{"type":23,"tag":150,"props":1575,"children":1577},{"class":152,"line":1576},24,[1578],{"type":23,"tag":150,"props":1579,"children":1580},{"style":156},[1581],{"type":29,"value":1582},"        }\n",{"type":23,"tag":150,"props":1584,"children":1586},{"class":152,"line":1585},25,[1587],{"type":23,"tag":150,"props":1588,"children":1589},{"style":162},[1590],{"type":29,"value":1591},"        finally\n",{"type":23,"tag":150,"props":1593,"children":1595},{"class":152,"line":1594},26,[1596],{"type":23,"tag":150,"props":1597,"children":1598},{"style":156},[1599],{"type":29,"value":1401},{"type":23,"tag":150,"props":1601,"children":1603},{"class":152,"line":1602},27,[1604,1609,1613,1617,1621,1626],{"type":23,"tag":150,"props":1605,"children":1606},{"style":432},[1607],{"type":29,"value":1608},"            channel",{"type":23,"tag":150,"props":1610,"children":1611},{"style":156},[1612],{"type":29,"value":440},{"type":23,"tag":150,"props":1614,"children":1615},{"style":432},[1616],{"type":29,"value":1534},{"type":23,"tag":150,"props":1618,"children":1619},{"style":156},[1620],{"type":29,"value":440},{"type":23,"tag":150,"props":1622,"children":1623},{"style":357},[1624],{"type":29,"value":1625},"Complete",{"type":23,"tag":150,"props":1627,"children":1628},{"style":156},[1629],{"type":29,"value":495},{"type":23,"tag":150,"props":1631,"children":1633},{"class":152,"line":1632},28,[1634],{"type":23,"tag":150,"props":1635,"children":1636},{"style":156},[1637],{"type":29,"value":1582},{"type":23,"tag":150,"props":1639,"children":1641},{"class":152,"line":1640},29,[1642,1647,1651],{"type":23,"tag":150,"props":1643,"children":1644},{"style":156},[1645],{"type":29,"value":1646},"    },",{"type":23,"tag":150,"props":1648,"children":1649},{"style":432},[1650],{"type":29,"value":1031},{"type":23,"tag":150,"props":1652,"children":1653},{"style":156},[1654],{"type":29,"value":460},{"type":23,"tag":150,"props":1656,"children":1658},{"class":152,"line":1657},30,[1659],{"type":23,"tag":150,"props":1660,"children":1661},{"emptyLinePlaceholder":502},[1662],{"type":29,"value":505},{"type":23,"tag":150,"props":1664,"children":1666},{"class":152,"line":1665},31,[1667,1672,1676,1681,1685,1690,1694,1698],{"type":23,"tag":150,"props":1668,"children":1669},{"style":162},[1670],{"type":29,"value":1671},"    return",{"type":23,"tag":150,"props":1673,"children":1674},{"style":156},[1675],{"type":29,"value":537},{"type":23,"tag":150,"props":1677,"children":1678},{"style":432},[1679],{"type":29,"value":1680},"channel",{"type":23,"tag":150,"props":1682,"children":1683},{"style":156},[1684],{"type":29,"value":440},{"type":23,"tag":150,"props":1686,"children":1687},{"style":432},[1688],{"type":29,"value":1689},"Reader",{"type":23,"tag":150,"props":1691,"children":1692},{"style":156},[1693],{"type":29,"value":547},{"type":23,"tag":150,"props":1695,"children":1696},{"style":432},[1697],{"type":29,"value":1344},{"type":23,"tag":150,"props":1699,"children":1700},{"style":156},[1701],{"type":29,"value":460},{"type":23,"tag":150,"props":1703,"children":1705},{"class":152,"line":1704},32,[1706],{"type":23,"tag":150,"props":1707,"children":1708},{"style":156},[1709],{"type":29,"value":1710},"}\n",{"type":23,"tag":916,"props":1712,"children":1714},{"id":1713},"consumer側の実装",[1715],{"type":29,"value":1716},"Consumer側の実装",{"type":23,"tag":140,"props":1718,"children":1720},{"className":394,"code":1719,"language":396,"meta":7,"style":7},"var (reader, generationTask) = Store.GetSoundsForPlayStreaming(Store.SeekIndex, cancellationToken);\n\n// reader経由でチャンネルの内容を取得する\nawait foreach (var sound in reader.ReadAllAsync(cancellationToken))\n{\n    await App.Instance.PlaySoundAsync(sound);\n}\n\nawait generationTask;\n",[1721],{"type":23,"tag":47,"props":1722,"children":1723},{"__ignoreMap":7},[1724,1801,1808,1816,1873,1880,1927,1934,1941],{"type":23,"tag":150,"props":1725,"children":1726},{"class":152,"line":17},[1727,1731,1735,1740,1744,1749,1753,1757,1762,1766,1771,1775,1780,1784,1789,1793,1797],{"type":23,"tag":150,"props":1728,"children":1729},{"style":416},[1730],{"type":29,"value":419},{"type":23,"tag":150,"props":1732,"children":1733},{"style":156},[1734],{"type":29,"value":537},{"type":23,"tag":150,"props":1736,"children":1737},{"style":357},[1738],{"type":29,"value":1739},"reader",{"type":23,"tag":150,"props":1741,"children":1742},{"style":156},[1743],{"type":29,"value":547},{"type":23,"tag":150,"props":1745,"children":1746},{"style":357},[1747],{"type":29,"value":1748}," generationTask",{"type":23,"tag":150,"props":1750,"children":1751},{"style":156},[1752],{"type":29,"value":557},{"type":23,"tag":150,"props":1754,"children":1755},{"style":156},[1756],{"type":29,"value":429},{"type":23,"tag":150,"props":1758,"children":1759},{"style":432},[1760],{"type":29,"value":1761}," Store",{"type":23,"tag":150,"props":1763,"children":1764},{"style":156},[1765],{"type":29,"value":440},{"type":23,"tag":150,"props":1767,"children":1768},{"style":357},[1769],{"type":29,"value":1770},"GetSoundsForPlayStreaming",{"type":23,"tag":150,"props":1772,"children":1773},{"style":156},[1774],{"type":29,"value":450},{"type":23,"tag":150,"props":1776,"children":1777},{"style":432},[1778],{"type":29,"value":1779},"Store",{"type":23,"tag":150,"props":1781,"children":1782},{"style":156},[1783],{"type":29,"value":440},{"type":23,"tag":150,"props":1785,"children":1786},{"style":432},[1787],{"type":29,"value":1788},"SeekIndex",{"type":23,"tag":150,"props":1790,"children":1791},{"style":156},[1792],{"type":29,"value":547},{"type":23,"tag":150,"props":1794,"children":1795},{"style":432},[1796],{"type":29,"value":1031},{"type":23,"tag":150,"props":1798,"children":1799},{"style":156},[1800],{"type":29,"value":460},{"type":23,"tag":150,"props":1802,"children":1803},{"class":152,"line":412},[1804],{"type":23,"tag":150,"props":1805,"children":1806},{"emptyLinePlaceholder":502},[1807],{"type":29,"value":505},{"type":23,"tag":150,"props":1809,"children":1810},{"class":152,"line":463},[1811],{"type":23,"tag":150,"props":1812,"children":1813},{"style":406},[1814],{"type":29,"value":1815},"// reader経由でチャンネルの内容を取得する\n",{"type":23,"tag":150,"props":1817,"children":1818},{"class":152,"line":498},[1819,1824,1829,1833,1837,1842,1846,1851,1855,1860,1864,1869],{"type":23,"tag":150,"props":1820,"children":1821},{"style":416},[1822],{"type":29,"value":1823},"await",{"type":23,"tag":150,"props":1825,"children":1826},{"style":162},[1827],{"type":29,"value":1828}," foreach",{"type":23,"tag":150,"props":1830,"children":1831},{"style":156},[1832],{"type":29,"value":537},{"type":23,"tag":150,"props":1834,"children":1835},{"style":416},[1836],{"type":29,"value":419},{"type":23,"tag":150,"props":1838,"children":1839},{"style":357},[1840],{"type":29,"value":1841}," sound",{"type":23,"tag":150,"props":1843,"children":1844},{"style":162},[1845],{"type":29,"value":1428},{"type":23,"tag":150,"props":1847,"children":1848},{"style":432},[1849],{"type":29,"value":1850}," reader",{"type":23,"tag":150,"props":1852,"children":1853},{"style":156},[1854],{"type":29,"value":440},{"type":23,"tag":150,"props":1856,"children":1857},{"style":357},[1858],{"type":29,"value":1859},"ReadAllAsync",{"type":23,"tag":150,"props":1861,"children":1862},{"style":156},[1863],{"type":29,"value":450},{"type":23,"tag":150,"props":1865,"children":1866},{"style":432},[1867],{"type":29,"value":1868},"cancellationToken",{"type":23,"tag":150,"props":1870,"children":1871},{"style":156},[1872],{"type":29,"value":1472},{"type":23,"tag":150,"props":1874,"children":1875},{"class":152,"line":508},[1876],{"type":23,"tag":150,"props":1877,"children":1878},{"style":156},[1879],{"type":29,"value":571},{"type":23,"tag":150,"props":1881,"children":1882},{"class":152,"line":565},[1883,1887,1892,1896,1901,1905,1910,1914,1919,1923],{"type":23,"tag":150,"props":1884,"children":1885},{"style":416},[1886],{"type":29,"value":719},{"type":23,"tag":150,"props":1888,"children":1889},{"style":432},[1890],{"type":29,"value":1891}," App",{"type":23,"tag":150,"props":1893,"children":1894},{"style":156},[1895],{"type":29,"value":440},{"type":23,"tag":150,"props":1897,"children":1898},{"style":432},[1899],{"type":29,"value":1900},"Instance",{"type":23,"tag":150,"props":1902,"children":1903},{"style":156},[1904],{"type":29,"value":440},{"type":23,"tag":150,"props":1906,"children":1907},{"style":357},[1908],{"type":29,"value":1909},"PlaySoundAsync",{"type":23,"tag":150,"props":1911,"children":1912},{"style":156},[1913],{"type":29,"value":450},{"type":23,"tag":150,"props":1915,"children":1916},{"style":432},[1917],{"type":29,"value":1918},"sound",{"type":23,"tag":150,"props":1920,"children":1921},{"style":156},[1922],{"type":29,"value":557},{"type":23,"tag":150,"props":1924,"children":1925},{"style":172},[1926],{"type":29,"value":645},{"type":23,"tag":150,"props":1928,"children":1929},{"class":152,"line":574},[1930],{"type":23,"tag":150,"props":1931,"children":1932},{"style":156},[1933],{"type":29,"value":1710},{"type":23,"tag":150,"props":1935,"children":1936},{"class":152,"line":648},[1937],{"type":23,"tag":150,"props":1938,"children":1939},{"emptyLinePlaceholder":502},[1940],{"type":29,"value":505},{"type":23,"tag":150,"props":1942,"children":1943},{"class":152,"line":713},[1944,1948,1952],{"type":23,"tag":150,"props":1945,"children":1946},{"style":416},[1947],{"type":29,"value":1823},{"type":23,"tag":150,"props":1949,"children":1950},{"style":432},[1951],{"type":29,"value":1748},{"type":23,"tag":150,"props":1953,"children":1954},{"style":156},[1955],{"type":29,"value":645},{"type":23,"tag":31,"props":1957,"children":1958},{},[1959],{"type":29,"value":1960},"このように、簡単にバックグラウンドスレッドからUIスレッドへのストリーミングを実現することができます。この機能自体は.NET標準なのでBlazor以外でも使うことができます。",{"type":23,"tag":228,"props":1962,"children":1964},{"id":1963},"別の不具合が発生autosaver-のスレッド安全性修正",[1965],{"type":29,"value":1966},"別の不具合が発生！AutoSaver のスレッド安全性修正",{"type":23,"tag":31,"props":1968,"children":1969},{},[1970],{"type":29,"value":1971},"再生処理は簡単に改善できましたが、PICOMに搭載していた自動セーブ機能で用いていたタイマーが壊れる不具合が発生しました。",{"type":23,"tag":31,"props":1973,"children":1974},{},[1975,1977,1982,1984,1989],{"type":29,"value":1976},"原因は、",{"type":23,"tag":47,"props":1978,"children":1980},{"className":1979},[],[1981],{"type":29,"value":52},{"type":29,"value":1983}," により ",{"type":23,"tag":47,"props":1985,"children":1987},{"className":1986},[],[1988],{"type":29,"value":84},{"type":29,"value":1990}," のコールバックが実際のバックグラウンドスレッドで実行されるようになり、PropertyChanged イベントチェーンがバックグラウンドから発火して Blazor のレンダリング競合を引き起こしたというものです。UIの更新はUIスレッドで実行しないといけないという制約の影響となります。",{"type":23,"tag":31,"props":1992,"children":1993},{},[1994,1996,2001,2002,2011],{"type":29,"value":1995},"修正方法は、 ",{"type":23,"tag":47,"props":1997,"children":1999},{"className":1998},[],[2000],{"type":29,"value":84},{"type":29,"value":86},{"type":23,"tag":42,"props":2003,"children":2005},{"content":2004},".NET 6で追加された非同期対応タイマー。awaitで次のティックを待てるため、asyncメソッド内で安全に使える",[2006],{"type":23,"tag":47,"props":2007,"children":2009},{"className":2008},[],[2010],{"type":29,"value":92},{"type":29,"value":2012}," に変更するだけです。",{"type":23,"tag":140,"props":2014,"children":2016},{"className":394,"code":2015,"language":396,"meta":7,"style":7},"// Before: System.Timers.Timer（コールバックがスレッドプールで実行される）\nprivate async void TimerCallback(object sender, ElapsedEventArgs e)\n{\n    await _saveAction.Invoke();\n}\n\n// After: PeriodicTimer（async コンテキストで安全に実行）\nprivate async Task RunAsync(CancellationToken cancellationToken)\n{\n    while (await _timer.WaitForNextTickAsync(cancellationToken))\n    {\n        await _saveAction.Invoke();\n    }\n}\n",[2017],{"type":23,"tag":47,"props":2018,"children":2019},{"__ignoreMap":7},[2020,2028,2083,2090,2115,2122,2129,2137,2174,2181,2223,2230,2254,2262],{"type":23,"tag":150,"props":2021,"children":2022},{"class":152,"line":17},[2023],{"type":23,"tag":150,"props":2024,"children":2025},{"style":406},[2026],{"type":29,"value":2027},"// Before: System.Timers.Timer（コールバックがスレッドプールで実行される）\n",{"type":23,"tag":150,"props":2029,"children":2030},{"class":152,"line":412},[2031,2036,2041,2046,2051,2055,2060,2065,2069,2074,2079],{"type":23,"tag":150,"props":2032,"children":2033},{"style":416},[2034],{"type":29,"value":2035},"private",{"type":23,"tag":150,"props":2037,"children":2038},{"style":416},[2039],{"type":29,"value":2040}," async",{"type":23,"tag":150,"props":2042,"children":2043},{"style":162},[2044],{"type":29,"value":2045}," void",{"type":23,"tag":150,"props":2047,"children":2048},{"style":357},[2049],{"type":29,"value":2050}," TimerCallback",{"type":23,"tag":150,"props":2052,"children":2053},{"style":156},[2054],{"type":29,"value":450},{"type":23,"tag":150,"props":2056,"children":2057},{"style":162},[2058],{"type":29,"value":2059},"object",{"type":23,"tag":150,"props":2061,"children":2062},{"style":357},[2063],{"type":29,"value":2064}," sender",{"type":23,"tag":150,"props":2066,"children":2067},{"style":156},[2068],{"type":29,"value":547},{"type":23,"tag":150,"props":2070,"children":2071},{"style":954},[2072],{"type":29,"value":2073}," ElapsedEventArgs",{"type":23,"tag":150,"props":2075,"children":2076},{"style":357},[2077],{"type":29,"value":2078}," e",{"type":23,"tag":150,"props":2080,"children":2081},{"style":156},[2082],{"type":29,"value":1045},{"type":23,"tag":150,"props":2084,"children":2085},{"class":152,"line":463},[2086],{"type":23,"tag":150,"props":2087,"children":2088},{"style":156},[2089],{"type":29,"value":571},{"type":23,"tag":150,"props":2091,"children":2092},{"class":152,"line":498},[2093,2097,2102,2106,2111],{"type":23,"tag":150,"props":2094,"children":2095},{"style":416},[2096],{"type":29,"value":719},{"type":23,"tag":150,"props":2098,"children":2099},{"style":432},[2100],{"type":29,"value":2101}," _saveAction",{"type":23,"tag":150,"props":2103,"children":2104},{"style":156},[2105],{"type":29,"value":440},{"type":23,"tag":150,"props":2107,"children":2108},{"style":357},[2109],{"type":29,"value":2110},"Invoke",{"type":23,"tag":150,"props":2112,"children":2113},{"style":156},[2114],{"type":29,"value":495},{"type":23,"tag":150,"props":2116,"children":2117},{"class":152,"line":508},[2118],{"type":23,"tag":150,"props":2119,"children":2120},{"style":156},[2121],{"type":29,"value":1710},{"type":23,"tag":150,"props":2123,"children":2124},{"class":152,"line":565},[2125],{"type":23,"tag":150,"props":2126,"children":2127},{"emptyLinePlaceholder":502},[2128],{"type":29,"value":505},{"type":23,"tag":150,"props":2130,"children":2131},{"class":152,"line":574},[2132],{"type":23,"tag":150,"props":2133,"children":2134},{"style":406},[2135],{"type":29,"value":2136},"// After: PeriodicTimer（async コンテキストで安全に実行）\n",{"type":23,"tag":150,"props":2138,"children":2139},{"class":152,"line":648},[2140,2144,2148,2152,2157,2161,2166,2170],{"type":23,"tag":150,"props":2141,"children":2142},{"style":416},[2143],{"type":29,"value":2035},{"type":23,"tag":150,"props":2145,"children":2146},{"style":416},[2147],{"type":29,"value":2040},{"type":23,"tag":150,"props":2149,"children":2150},{"style":954},[2151],{"type":29,"value":985},{"type":23,"tag":150,"props":2153,"children":2154},{"style":357},[2155],{"type":29,"value":2156}," RunAsync",{"type":23,"tag":150,"props":2158,"children":2159},{"style":156},[2160],{"type":29,"value":450},{"type":23,"tag":150,"props":2162,"children":2163},{"style":954},[2164],{"type":29,"value":2165},"CancellationToken",{"type":23,"tag":150,"props":2167,"children":2168},{"style":357},[2169],{"type":29,"value":1031},{"type":23,"tag":150,"props":2171,"children":2172},{"style":156},[2173],{"type":29,"value":1045},{"type":23,"tag":150,"props":2175,"children":2176},{"class":152,"line":713},[2177],{"type":23,"tag":150,"props":2178,"children":2179},{"style":156},[2180],{"type":29,"value":571},{"type":23,"tag":150,"props":2182,"children":2183},{"class":152,"line":730},[2184,2189,2193,2197,2202,2206,2211,2215,2219],{"type":23,"tag":150,"props":2185,"children":2186},{"style":162},[2187],{"type":29,"value":2188},"    while",{"type":23,"tag":150,"props":2190,"children":2191},{"style":156},[2192],{"type":29,"value":537},{"type":23,"tag":150,"props":2194,"children":2195},{"style":416},[2196],{"type":29,"value":1823},{"type":23,"tag":150,"props":2198,"children":2199},{"style":432},[2200],{"type":29,"value":2201}," _timer",{"type":23,"tag":150,"props":2203,"children":2204},{"style":156},[2205],{"type":29,"value":440},{"type":23,"tag":150,"props":2207,"children":2208},{"style":357},[2209],{"type":29,"value":2210},"WaitForNextTickAsync",{"type":23,"tag":150,"props":2212,"children":2213},{"style":156},[2214],{"type":29,"value":450},{"type":23,"tag":150,"props":2216,"children":2217},{"style":432},[2218],{"type":29,"value":1868},{"type":23,"tag":150,"props":2220,"children":2221},{"style":156},[2222],{"type":29,"value":1472},{"type":23,"tag":150,"props":2224,"children":2225},{"class":152,"line":739},[2226],{"type":23,"tag":150,"props":2227,"children":2228},{"style":156},[2229],{"type":29,"value":1283},{"type":23,"tag":150,"props":2231,"children":2232},{"class":152,"line":747},[2233,2238,2242,2246,2250],{"type":23,"tag":150,"props":2234,"children":2235},{"style":416},[2236],{"type":29,"value":2237},"        await",{"type":23,"tag":150,"props":2239,"children":2240},{"style":432},[2241],{"type":29,"value":2101},{"type":23,"tag":150,"props":2243,"children":2244},{"style":156},[2245],{"type":29,"value":440},{"type":23,"tag":150,"props":2247,"children":2248},{"style":357},[2249],{"type":29,"value":2110},{"type":23,"tag":150,"props":2251,"children":2252},{"style":156},[2253],{"type":29,"value":495},{"type":23,"tag":150,"props":2255,"children":2256},{"class":152,"line":768},[2257],{"type":23,"tag":150,"props":2258,"children":2259},{"style":156},[2260],{"type":29,"value":2261},"    }\n",{"type":23,"tag":150,"props":2263,"children":2264},{"class":152,"line":789},[2265],{"type":23,"tag":150,"props":2266,"children":2267},{"style":156},[2268],{"type":29,"value":1710},{"type":23,"tag":24,"props":2270,"children":2272},{"id":2271},"wasmenablethreadsを有効化したメリットデメリット",[2273],{"type":29,"value":2274},"WasmEnableThreadsを有効化したメリット・デメリット",{"type":23,"tag":31,"props":2276,"children":2277},{},[2278],{"type":29,"value":2279},"何よりも動作が圧倒的に早くなったという点があります。これまでバックグラウンドで音声を生成している際にUIの動作が明らかにカクついていたのですが、これが完全に改善されました。このおかげでUIの描画やデザインを考える際にビジネスロジックのリソースなどを気にせずに実装できるようになりました。",{"type":23,"tag":31,"props":2281,"children":2282},{},[2283],{"type":29,"value":2284},"デメリットは今のところ大きくは感じていませんが、まだ歴史の浅い機能ということや、マルチスレッド化によってプログラムが複雑化するという問題はあると思います。コードの秩序を保つためにもどのような仕組みかは文書化しておくなど開発工程的な部分で工夫が必要だと思います（今回の記事もこれが目的です）。",{"type":23,"tag":24,"props":2286,"children":2288},{"id":2287},"まとめ",[2289],{"type":29,"value":2287},{"type":23,"tag":101,"props":2291,"children":2292},{},[2293,2311,2321,2331],{"type":23,"tag":105,"props":2294,"children":2295},{},[2296,2297,2302,2304,2309],{"type":29,"value":63},{"type":23,"tag":47,"props":2298,"children":2300},{"className":2299},[],[2301],{"type":29,"value":52},{"type":29,"value":2303}," を有効化すれば、",{"type":23,"tag":47,"props":2305,"children":2307},{"className":2306},[],[2308],{"type":29,"value":200},{"type":29,"value":2310}," で本物のバックグラウンドスレッドが使える",{"type":23,"tag":105,"props":2312,"children":2313},{},[2314,2319],{"type":23,"tag":47,"props":2315,"children":2317},{"className":2316},[],[2318],{"type":29,"value":224},{"type":29,"value":2320}," を使うため COOP/COEP ヘッダーが必須。開発環境では ASP.NET Core ホストプロジェクトでミドルウェアから設定するのが手軽",{"type":23,"tag":105,"props":2322,"children":2323},{},[2324,2329],{"type":23,"tag":47,"props":2325,"children":2327},{"className":2326},[],[2328],{"type":29,"value":76},{"type":29,"value":2330}," の Producer-Consumer パターンで、バックグラウンド生成とストリーミング再生を自然に分離できる",{"type":23,"tag":105,"props":2332,"children":2333},{},[2334,2339,2341,2346,2348,2353],{"type":23,"tag":47,"props":2335,"children":2337},{"className":2336},[],[2338],{"type":29,"value":84},{"type":29,"value":2340}," はスレッドプールでコールバックが走るため、",{"type":23,"tag":47,"props":2342,"children":2344},{"className":2343},[],[2345],{"type":29,"value":52},{"type":29,"value":2347}," 有効化後に Blazor のレンダリング競合を起こす。",{"type":23,"tag":47,"props":2349,"children":2351},{"className":2350},[],[2352],{"type":29,"value":92},{"type":29,"value":2354}," に置き換えれば async コンテキストで安全に動作する",{"type":23,"tag":2356,"props":2357,"children":2358},"style",{},[2359],{"type":29,"value":2360},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":412,"depth":412,"links":2362},[2363,2364,2365,2369,2370],{"id":26,"depth":412,"text":26},{"id":97,"depth":412,"text":97},{"id":122,"depth":412,"text":125,"children":2366},[2367,2368],{"id":206,"depth":463,"text":209},{"id":334,"depth":463,"text":337},{"id":2271,"depth":412,"text":2274},{"id":2287,"depth":412,"text":2287},"markdown","content:articles:tech:blazor:blazor-wasm-multithreading.md","content","articles/tech/blazor/blazor-wasm-multithreading.md","articles/tech/blazor/blazor-wasm-multithreading","md",1776519960302]