ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swiftの並行処理でデータ競合を排除する
Swift Concurrencyのコア概念の1つであるタスクとアクタの分離について解説します。データ競合を排除するためのSwiftのアプローチと、Appアーキテクチャに対するその効果について解説します。また、ご利用のコードにおけるアトミック性の重要性をはじめ、分離の維持におけるSendableチェックの微妙な差異や、並行システムでの作業のオーダーに関する前提条件の再検討について解説します。
リソース
関連ビデオ
WWDC22
WWDC21
-
ダウンロード
♪ 落ち着いた雰囲気のヒップホップ音楽 ♪ ♪ こんにちは Swift teamのDougです 本日お話する内容は Swift Concurrency のデータ競合排除方法です Swift Concurrency は並行プログラムを 書きやすくする言語機能です 個々の言語機能の構造に関しては 2021 WWDC トークをご覧ください 本日のトークではデータ競合なしで 並行処理を行うためのプログラムを構造する 方法としてSwift Concurrency の 包括的な側面をご紹介します しかしこれを説明するには 優れた類推が必要です そこで並行処理の公海を 一緒に進んでいきましょう 並行処理の公海は予測不可能なものです 多くのことが同時に発生します しかし皆さんが舵を取りSwift により支援を 受けることで素晴らしいことが生まれます では 始めましょう まずは隔離についてお話しします これは Swift の並行処理 モデルで重要なものの1つで データ競合が起こる方法で データが共有されないことを確保します タスク隔離から始めましょう 並行処理の海ではタスクは船となります 船は主要な作業者です 船の仕事は最初から最後まで 順番に仕事を完了することです 船は非同期で コードに 「await」演算子があれば 作業を何度でも中断できます さらに 自己完結型なので 各タスクには独自のリソースがあり それぞれの船が独自で作業を行います 船が完全に独立している場合 データ競合なしで並行処理を行えますが お互いに通信する方法が なければ 役に立ちません では通信を追加しましょう 例えば1隻の船には 他の船と共有したいパイナップルがあります そこで船は公海で会い パイナップルを船から船へと移動させます ここで物理的な類推が 少し役に立たなくなります なぜならこのパイナップルは 船から船へと移動できる物理的な物ではなく データだからですSwift ではそのデータを 表現するための方法が2つあります パイナップルの種類を定義する方法は? Swift では値を使います なのでこのパイナップルを 重さと成熟度で定義されるStruct にします ではどう作用するか見てみましょう 船が航海で会い パイナップルのインスタンスのコピーを 船から船へと移動させ どちらの船も独自のコピーを持ち去ります slice() や ripen()メソッドを呼び出して コピーを変化するとしても 別の船のコピーには影響が及びません Swift には常に望ましい値型があり それが理由で 変化はローカルにしか影響を及ぼしません この原理により値型は隔離を保ちます ではデータモデルを拡張し チキンを足しましょう パイナップルはほぼ食料としてしか 使用できませんがチキンは独特の性格があり すばらしい生き物です 次のようにクラスを使いモデルを作ります 勇敢な船乗りたちがチキンを交換するとします 船同士が会うとチキンを共有しますが チキンのような参照型をコピーしても 完全なコピーを得ることはできず その特定のオブジェクトの参照を得ます そのため船がそれぞれの航路に進むと 問題があることがわかります どちらの船も独立しておらず 並行して作業を行います 同じチキンオブジェクトを参照するからです 一隻の船はチキンにエサをやり もう一隻はチキンと遊ぼうとする場合に チキンが混乱してしまい共有した変形データが データ競合を引き起こしてしまいます ボート間でのパイナップルの 共有が安全なことを 知るための方法は必要ですが チキンでは無効です また Swift コンパイラで確認が行われ チキンがうっかり船から船へと パスされないことを確実にする必要があります Swift プロトコルは種類の分類に最適で 動作について論証することができます Sendable プロトコルはタイプの説明に使用され データ競合を起こさずに異なる隔離ドメインで 安全に共有できます タイプは適合性を書くことで Sendable にできます パイナップルの Struct が Sendable に一致したのは 値型だからですチキンクラスでは不可なのは 非同期の参照型だからです Sendableをプロトコルとしてモデル化すれば 隔離ドメイン全体でデータが共有される 場所を説明することができます 例えばタスクが値を返した場合 この値はその値を待っている 任意のタスクに提供されます ここではチキンをタスクから 返そうとしていますが 「安全ではない」という エラーメッセージを受けます チキンが Sendable ではないからです 実際の Sendable 制約はタスク構造自体の 説明から来ますこれは Success という 結果型のタスクがSendable プロトコルに 準拠しなければならないと指定します Sendable 制約は異なる 隔離ドメインを通し 値がパスされる 一般的なパラメータがある場合に使用します 船の間でデータを共有する アイデアに戻りましょう 公海で2隻の船がデータを共有したい場合 すべてのものが安全に共有できることを 確認する担当者が必要です それはフレンドリーな税関検査官の役目で ここでは Swiftコンパイラが担当し Sendable 型だけが交換されるようにします Sendable であるパイナップルは 自由に交換することができますが チキンを交換することはできません フレンドリーな税関検査官が そのミスを防ぎます コンパイラはさまざまな場所で Sendable の 正確性を確認します Sendable 型は構造により修正される必要があり 共有されたいかなるデータも 取り入れることはできません Enums と structs は一般的に値型を定義し すべてのインスタンスデータをコピーして 個別の値を生成します そのため すべてのインスタンスデータが Sendable であればSendable になれます Sendable はコレクションや 条件付き適合を使用する その他の一般的タイプで伝搬することができます 一連の Sendable タイプはSendable であるため 箱一杯のパイナップルもSendable になります これらすべてのSendable の適合性は Swift コンパイラが非公開型向けに推測します なので成熟度パイナップル 箱は すべて暗黙にSendable になります しかし鳥小屋のためにCoop を作りましょう このタイプはSendable にできません 非Sendable 状態が含まれているからです チキンは Sendableでないため一連のチキンも Sendable ではありません コンパイラはエラーメッセージで このタイプは安全に共有できないと伝えます クラスは参照型なので 最終クラスにイミュータブル ストレージしかない場合など 非常に限られた状況において Sendable にすることができます チキンのクラスをSendable にしようとすると 可変状況が含まれているため エラーが発生します ロックを一貫して使用する場合など 内部で同期を行う参照タイプを 実行するのは可能です このようなタイプは概念的にSendable ですが Swift が推論することはできません 未確認の Sendable で確認を無効にします これには気をつけてください ミュータブルな状況を 未確認の Sendable 経由で持ち込むと Swift が 提供するデータ競合への 安全保証が弱体するからです タスクの作成には船からこぎ船を出すように 新しい個別のタスクでの クロージャの実行が伴い これを行うことで元のタスクから値を取得し 新しいタスクへとパスできるので Sendable でデータ競合が 起こらないことを確認する必要があります この境界でSendable でないものを 共有しようとするとSwift がこのような エラーメッセージを出します これはタスクの作成において 魔法ではありません クロージャは Sendable At-sendable で明確に 書くことができるクロージャに推論されます Sendable クロージャは Sendable 関数型の値です At-Sendable は関数型がSendable プロトコルに 同調するのを示すために 関数型で書くことができます それにより関数型の値が暗示され 他の隔離ドメインにパスし データ競合を起こさずに 呼び出すことができます 通常関数型はプロトコルに 同調することはできませんが Sendable は特別ですなぜならコンパイラは Sendable 向けに意味要件を検証するからです タプルの Sendable 型にも 同様のサポートがあり Sendable プロトコルに同調し 言語全体で Sendable を 使用可能にします 今説明したシステムにはそれぞれが隔離した 多数のタスクが同時実行されます Sendable プロトコルは タスク間で安全に共有できる タイプを説明し Swift はあらゆる段階でSendable の同調を 確認しタスクの隔離を確保します でも共有されたミュータブル データの概念がない場合は タスクは有意義な方法で 調整することが難しくなります そのため何らかの方法で タスクでデータを共有し データ競合が生じないように する必要があります ここで Actor が登場します Actors はタスク別に隔離状態に アクセスできる方法を提供しますが 調整された方法によりデータ競合を排除します Actors は同調性の海にある島となります 船のようにそれぞれの島は自己完結型で 海の他の場所から隔離され 独自の状態を保ちます その状態にアクセスするにはその島でコードを 実行する必要があります 例えばadvanceTime メソッドは この島向けに隔離されています この島に住みこの島のすべての状態に アクセスできます 島でコードを実行するには船が必要で 船は島に行き コードを実行することができ その時にその状態にアクセスできます 島では一度に1隻の船のみがコードを実行でき 島の状態に同時アクセスできないように します 他の船が来ても島を訪問するには 順番に待つ必要があります 船が島を訪問できるまで 長時間かかる場合がありActor に入ることは 保留ポイントとなりawait と記されます 島が空くと再び保留ポイントで 次の船が訪問できます 公海で2隻の船が会うように 船と島とのやりとりは非 Sendable 型が この2つ間を通過しないように どちらも隔離している必要があります 例えば船から島に チキンを足すとすると 異なる隔離ドメインから 同じチキンの参照が2つ作成され Swift コンパイラはそれを却下します 同様に島からペット用のチキンをもらい 船で連れて帰ろうとすると Sendable は データ競合が起こってはいけないことを確認 Actors は参照型ですがクラスとは異なり プロパティとコードは 隔離され同時アクセスを防ぎ 異なる隔離ドメインからの参照を Actor が保有することが安全です 島にマップがあるようなもので マップを使い島に行くことはできますが その状態にアクセスするには ドッキングの手順を 踏む必要があるため すべての Actor 型が明示的に Sendable です どこコードが Actor に対し隔離され どれがされていないかを知る方法ですが Actor の隔離はその時の文脈で決定します Actor のインスタンスプロパティは その Actor に隔離されています インスタンスメソッドや拡張も advanceTime のように デフォルトで隔離しています 低減アルゴリズムにパスされたクロージャなど Sendable ではないものはActor に残り Actor 隔離文脈にいる時に Actor 隔離になります タスク初期化子もその文脈から Actor 隔離を 継承するので作成されたタスクは 初期化された同じ Actor で スケジュールされます それによりフロックへのアクセスが承認され 一方で切り離されたタスクは 文脈から Actor隔離を継承しません なぜなら作成された文脈から 完全に 独立しているからです ここに表示されているクロージャのコードは await を使用し隔離した食料のプロパティを 使用する必要がありActor 外と見なされます このクロージャは非隔離コードと呼ばれ 非隔離コードは Actor で実行されません 非隔離のキーワードを使用して Actor の 非隔離内で関数を明確に作成し Actor の外部に配置することができます 分離したタスクのクロージャの使用の 明確さと同じです つまり Actor に隔離された状態の一部を 読み込みたい場合はawait を使用して島に行き 必要な状態のコピーを取得する必要があります 非隔離の非同期のコードは 常に世界規模のプールで実行されます 船が公海に出た時だけ 実行されると考えてください つまり 作業を行うためには その島を去る必要があり 非 Sendable のデータを 一緒に持ち帰らないことを 確実にする必要があります ここではコンパイラが潜在的 データ競合を検知しました 非 Sendable のチキンが 島を去ろうとしています 別の非隔離コードの例を見てみましょう Greet の関数は非隔離の同期コードです 一般的に船や島や同調性のことを まったく知りません ここでは Actor 隔離のgreetOne 関数から 呼んでみます それでも大丈夫 この同期コードは 島から呼ばれると島に残ります フロックから自由にチキンを操作できます 代わりに greet という非隔離で非同期の 関数を使用する場合 greet は公海で船で実行されます ほとんどのSwift コードはこのように actor に同期し隔離されておらず 与えられたパラメータのみで実行されます そのため呼び出された隔離ドメインに残ります Actors はプログラムの残りから隔離された 状態を保ちます Actor で実行可能なタスクは1つのみであり その状態に同時アクセスはできません Sendable はタスクが入退室した場合に 非同期のミュータブル状態が 逃れないことを確認します これらすべてによりactor は 同期プログラムの構築ブロックの1つとなります main actor と呼ばれる 特別な actor もあります main actor を海の真ん中にある 大きな島だと考えてください これはすべての描画や ユーザインターフェースで起こる やりとりを表現する主要なスレッドです そのため何か描きたい場合は main actor の島でコードを 実行する必要があります UI には非常に重要で 「UIランド」とでも呼ぶべきです main actor は大きいと言いましたが これはプログラムのユーザインターフェースに 関連するさまざまな状態を 含んでいるという意味です UI フレームワークとAppには 実行される必要のある コードがたくさんあります それでもこれはactor ですので 一度に1つのジョブしか実行できません そのため main actorに大量の作業や 長期的な作業を実行しないことが大切です UI が反応しなくなるためです main actor への隔離は MainActor 属性で表現されます この属性を関数やクロージャに適用し main actor で実行される 必要があることを示します そうするとこのコードが隔離されたと言えます Swift コンパイラは main actor 隔離コードがメインスレッドのみで 実行されることを確保し同じ構造を使用して 他の actor にもアクセス可能にします main actor に隔離されていない 文脈から updateView が呼ばれた場合 await を使用して main actor に切り替わる必要があります main actor 属性は型に適用することもでき その場合これらの型のインスタンスは main actor に隔離されます 繰り返しますがこれは他の actor と同じで プロパティはmain actor にある時のみ アクセス可能で メソッドは 明確にオプトアウトしないと main actor に隔離されます 普通の actor のように main actor クラスの参照は データが データが隔離されているためSendable です これにより main actorの注釈は フレームワーク自体で メインスレッドにつなげる必要がある UI ビューやビューコントローラに適します ビューコントローラの参照を 他のタスクやプログラムのactor と共有でき ビューコントローラに非同期で呼び戻して 結果を投稿でき Appのアーキテクチャに直接影響を及ぼします Appでは ビューとコントローラは main actor に位置します 他のプログラムロジックはmain actor と分離し 他の actor を使用して 共有の状態や個別の作業を 説明するタスクを安全にモデル化します また これらのタスクは必要に応じて main actorと他のactor間で行き来できます 同調Appでは多くのことが起こっており それらを理解するのに役立つ ツールを構築しました 詳細については 「Swiftの並行処理を視覚化して最適化する」 を ご覧ください ではより深く潜って 原子性についてお話しします Swift Concurrencyモデルの目標は データ競合を排除することです これはデータ破損に関連する 低レベルのデータ競合を なくすことを意味しますが 高レベルで原子性を推論する必要があります 先述のように actor は一度に1つのタスクしか実行できません しかし actor での実行を停止すると actor は別のタスクを実行することができます これによりプログラムは確実に進捗し デッドロックの可能性を除外します しかしそれには await命令文を中心に actor の不変条件を慎重に 考慮する必要があります そうでなければ高レベルのデータ競合が起こり データが破損していないにも関わらず プログラムは予期せぬ状態になります この例を説明しましょう これは追加のパイナップルをいくつか 島に預けるための関数です actor 外にあるので 非隔離の非同期のコードです つまり公海で実行されます パイナップルとパイナップルを預けるはずの 島のマップがあります 最初の操作は 島から食料の配列のコピーを取得することです それには船が島を訪問する必要があり await キーワードにより信号を受けます 食料のコピーを取得した瞬間に 船は作業を続けるために 公海に向かいます これはパイナップル パラメータのパイナップルを 島から得た2つに追加するという意味です これで関数の最後の行に移ります 船は再び島を訪問し この3つのパイナップルに 島の食料配列を設定します ここではすべてが順調に行き 島には3つのパイナップルがありますが 違うことが起きる可能性もあります 最初の船が島を訪問するために待っている間 海賊船が来てパイナップルを すべて盗んだとします 元の船は島に3つのパイナップルを 預けますが問題に気づきます 3つのパイナップルが突然 5つになりました どうしてでしょう? 同じ actor でアクセスするために await が2つあるのに気が付きましたか? ここでは島の食料配列は この2つの await の間で 変化しないと推定しています しかしこれらはawait であるため タスクはここで保留になる可能性があり actor は海賊船と戦うなど より優先度の高い 作業にかかる可能性があります この特定のケースではSwift コンパイラは 別の actor の明白な状態を却下します しかしこのように預ける演算を actor で同期コードとして書き直すべきです これは同期コードであるため 中断されることなくactor で実行されます そのため島の状態は関数全体を通して 誰にも変更されないことを確実にできます actor を書く場合は どんな方法でも交互的になれる同期の トランザクション演算を考慮してください それぞれが actor が退出する際 良好な状態であることを 確保する必要があります 非同期の actor 演算はシンプルにします 主に同期のトランザクション演算から 形成し 各 await 演算でactor が いい状態になるようにします そうすれば actors を 低レベルと高レベル両方の データ競合でフルに活用できます 並行プログラムでは 一度に多くのことが起こります そのためそれらが起こる順序は 実行ごとに変わります それでもプログラムは一定の順序で 処理することに依存することがあります 例えばユーザ入力やサーバからの メッセージによる一連のイベントは イベントストリームが入って来ると その効果が順番に起こることを期待します 演算の順序を指定するツールはありますが actors はそのツールにはなりません Actors は最優先の作業を実行して 全体のシステムの反応を保ちます これは優先順位の逆転を排除し 同じ actor で優先度の低い作業が 高いものよりも先に実行されることを防ぎます これは先入れ先出しで実行する 連続ディスパッチキューとは 非常に異なることにご留意ください 作業順序を指定するための ツールはいくつかあります 1つ目は先ほどからよく口にしているタスクです タスクは使いなれた通常の コントロールフローで 最初から最後まで実行するため 自然に作業を命令します AsyncStream はイベント ストリームをモデル化します 1つのタスクはfor-await-in ループで 一連のイベントで反復され 各イベントを順番に処理し AsyncStream は任意の イベントプロデューサと共有でき 順序を保ちながらストリームに 要素を追加できます Swift の Concurrencyモデルが 隔離の概念を使用しSendable がタスクと actor の境界を確認して データ競合を除外するよう 設計されていると何度もお話ししました しかし作業を停止してSendable 型を あらゆる場所でマークすることはできません その代わりに漸進的な方法が必要です Swift 5.7 の構築設定では コンパイラの送信可能性の 確認の厳密性を設定できます デフォルトでは最小に設定されており コンパイラは明確にSendable として 示そうとされた場所だけを確認します これは Swift 5.5 や5.6 と似ており それ以上では警告やエラーは発生しません Sendable 適合性を追加した場合 チキンが Sendable でないため コンパイラは Coop 型は Sendable に できないと苦情を言います しかしこのようなSendable 関連の問題は 問題に1つずつ対処するために Swift 5 ではエラーではなく 警告として表示されます データ競合安全性をさらに向上するには 目標のある厳密な並行設定にします この設定では Sendable はasync/await タスク actors など すでにConcurrency機能を 採用しているコードを確認します 例えば 新しく作成された タスクで非 Sendable 型の値の取得の 試みを特定します 別のモジュールから来る 非 Sendable 型もあります まだ Sendable に更新されていない パッケージがあるか自身のモジュールで 更新する機会がない場合などです このような場合は@preconcurrency 属性で そのモジュールから来る型向けに Sendable 警告を一時的に無効にできます これでこのソースファイルの チキン型への警告を 止めることができます ある時点でFarmAnimals モジュールは 更新された Sendable並行を得ます そうなると次の2つのいずれかが起こります 何らかの方法でチキンがSendable になり preconcurrency 属性をインポートから 削除できるようになるか チキンは非 Sendable として認識され 警告が再び表示され チキンが Sendable であるという推定は 間違っていることを通告します 目標のある厳密な設定は 既存のコードとの互換性と 潜在的なデータ競合の 特定の間でバランスを保ちますが 競合が起こりうるすべの場所を 確認したい場合は 完全確認というオプションがあります これは意図される Swift 6 セマンティックを概算し データ競合を完全に取り除きます 最初の2つのモードの確認をすべて行いますが モジュールのすべての コードでも確認を行います ここでは Swift Concurrency 機能を まったく使用しません 代わりにディスパッチキューで作業を行い そのコードを同時に実行します ディスパッチキューでの非同期の演算は Sendable クロージャを取ることで知られており コンパイラは警告を発し非 Sendable body が ディスパッチキューで実行されたコードにより キャプチャされた場合に 競合が起こることを示します これは body パラメータを Sendable にして修正します その変更でこの警告は除外され すべてのdoWork 呼び出し元は Sendable クロージャの 提供が必要なことを知ります それによりデータ競合がより確認しやすくなり そしてここでは訪問関数が データ競合の元であるのがわかります 完全確認を使用するとプログラムの 潜在的なデータ競合を完全に排除します データ競合の排除を達成するために 最終的に完全確認を 使用する必要があるでしょう 漸進的にその目標に向かうことをお勧めします Swift Concurrencyモデルを導入して データ競合の安全性の高いAppを構築し より厳密な並行性の確認に移り コードから発生するエラークラスを排除します インポートを@preconcurrency で示し 警告を抑制することに 不安になる必要はありません これらのモジュールが より 厳しい確認を行うことにより コンパイラは皆さんの推測を再確認します 最終的に皆さんのコードはメモリ安全性と データ競合安全性の両方でメリットを受け すばらしいAppの構築に役立ちます 並行性の航海にご一緒頂き ありがとうございました ♪
-
-
1:18 - Tasks
Task.detached { let fish = await catchFish() let dinner = await cook(fish) await eat(dinner) }
-
2:31 - What is the pineapple?
enum Ripeness { case hard case perfect case mushy(daysPast: Int) } struct Pineapple { var weight: Double var ripeness: Ripeness mutating func ripen() async { … } mutating func slice() -> Int { … } }
-
3:15 - Adding chickens
final class Chicken { let name: String var currentHunger: HungerLevel func feed() { … } func play() { … } func produce() -> Egg { … } }
-
4:35 - Sendable protocol
protocol Sendable { }
-
4:44 - Use conformance to specify which types are Sendable
struct Pineapple: Sendable { … } //conforms to Sendable because its a value type class Chicken: Sendable { } // cannot conform to Sendable because its an unsynchronized reference type.
-
4:57 - Check Sendable across task boundaries
// will get an error because Chicken is not Sendable let petAdoption = Task { let chickens = await hatchNewFlock() return chickens.randomElement()! } let pet = await petAdoption.value
-
5:26 - The Sendable constraint is from the Task struct
struct Task<Success: Sendable, Failure: Error> { var value: Success { get async throws { … } } }
-
6:23 - Sendable checking for enums and structs
enum Ripeness: Sendable { case hard case perfect case mushy(daysPast: Int) } struct Pineapple: Sendable { var weight: Double var ripeness: Ripeness }
-
6:52 - Sendable checking for enums and structs with collections
//contains an array of Sendable types, therefore is Sendable struct Crate: Sendable { var pineapples: [Pineapple] }
-
7:17 - Sendable checking for enums and structs with non-Sendable collections
//stored property 'flock' of 'Sendable'-conforming struct 'Coop' has non-sendable type '[Chicken]' struct Coop: Sendable { var flock: [Chicken] }
-
7:36 - Sendable checking in classes
//Can be Sendable if a final class has immutable storage final class Chicken: Sendable { let name: String var currentHunger: HungerLevel //'currentHunger' is mutable, therefore Chicken cannot be Sendable }
-
7:58 - Reference types that do their own internal synchronization
//@unchecked can be used, but be careful! class ConcurrentCache<Key: Hashable & Sendable, Value: Sendable>: @unchecked Sendable { var lock: NSLock var storage: [Key: Value] }
-
8:21 - Sendable checking during task creation
let lily = Chicken(name: "Lily") Task.detached {@Sendable in lily.feed() }
-
9:08 - Sendable function types
struct Task<Success: Sendable, Failure: Error> { static func detached( priority: TaskPriority? = nil, operation: @Sendable @escaping () async throws -> Success ) -> Task<Success, Failure> }
-
10:28 - Actors
actor Island { var flock: [Chicken] var food: [Pineapple] func advanceTime() }
-
11:03 - Only one boat can visit an island at a time
func nextRound(islands: [Island]) async { for island in islands { await island.advanceTime() } }
-
11:34 - Non-Sendable data cannot be shared between a task and actor
//Both examples cannot be shared await myIsland.addToFlock(myChicken) myChicken = await myIsland.adoptPet()
-
12:43 - What code is actor-isolated?
actor Island { var flock: [Chicken] var food: [Pineapple] func advanceTime() { let totalSlices = food.indices.reduce(0) { (total, nextIndex) in total + food[nextIndex].slice() } Task { flock.map(Chicken.produce) } Task.detached { let ripePineapples = await food.filter { $0.ripeness == .perfect } print("There are \(ripePineapples.count) ripe pineapples on the island") } } }
-
14:03 - Nonisolated code
extension Island { nonisolated func meetTheFlock() async { let flockNames = await flock.map { $0.name } print("Meet our fabulous flock: \(flockNames)") } }
-
14:48 - Non-isolated synchronous code
func greet(_ friend: Chicken) { } extension Island { func greetOne() { if let friend = flock.randomElement() { greet(friend) } } }
-
15:15 - Non-isolated asynchronous code
func greet(_ friend: Chicken) { } func greetAny(flock: [Chicken]) async { if let friend = flock.randomElement() { greet(friend) } }
-
17:01 - Isolating functions to the main actor
@MainActor func updateView() { … } Task { @MainActor in // … view.selectedChicken = lily } nonisolated func computeAndUpdate() async { computeNewValues() await updateView() }
-
17:38 - @MainActor types
@MainActor class ChickenValley: Sendable { var flock: [Chicken] var food: [Pineapple] func advanceTime() { for chicken in flock { chicken.eat(from: &food) } } }
-
19:58 - Non-transactional code
func deposit(pineapples: [Pineapple], onto island: Island) async { var food = await island.food food += pineapples await island.food = food }
-
20:56 - Pirates!
await island.food.takeAll()
-
21:57 - Modify `deposit` function to be synchronous
extension Island { func deposit(pineapples: [Pineapple]) { var food = self.food food += pineapples self.food = food } }
-
23:56 - AsyncStreams deliver elements in order
for await event in eventStream { await process(event) }
-
25:02 - Minimal strict concurrency checking
import FarmAnimals struct Coop: Sendable { var flock: [Chicken] }
-
25:21 - Targeted strict concurrency checking
@preconcurrency import FarmAnimals func visit(coop: Coop) async { guard let favorite = coop.flock.randomElement() else { return } Task { favorite.play() } }
-
26:53 - Complete strict concurrency checking
import FarmAnimals func doWork(_ body: @Sendable @escaping () -> Void) { DispatchQueue.global().async { body() } } func visit(friend: Chicken) { doWork { friend.play() } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。