Delphiでスレッドを使用するさまざまな方法を示す

あまりにも多くのDelphiユーザーがスレッドを考えるという間違いを犯していることは、アプリケーションのパフォーマンスを改善するある種の魔法です。残念ながら、これは真実とはほど遠いものです。スレッドを実装しようとする際の最大の最大の間違いは、アプリケーションの視覚的コントロールに直接アクセスすることです。ただし、これらの視覚制御は、アプリケーションのメインスレッドのコンテキストでのみ機能します。別のスレッドを使用して、ユーザーインターフェイス内のコントロールを更新する必要があります。非常に慎重に計画および実装する必要があります。そして、ほとんどの場合、それはおそらく問題に対する正しい解決策ではありません。
簡単に言えば、DelphiのVCLフレームワークは安全ではありません。スレッドをUIに統合するには多くの方法がありますが、1つのサイズのすべてのソリューションはありません。それはあなたが達成しようとしていることによって常に異なります。ほとんどの場合、人々はより良いパフォーマンス(速度)を達成したいと考えています。しかし、それはスレッドを使用することによって行われることはほとんどありません。代わりに、スレッドがユーザーインターフェイスに統合されている最も一般的なシナリオは、長いタスク中にそのUIの応答性を維持することです。
このためには、クリック時にインターネットからファイルをダウンロードする1つのボタンのみを備えた単純なアプリケーションを想像します。アプリケーションには、UI全体に使用されるメインスレッドがすでに1つあります。 Windowsプラットフォームでは、Windowsメッセージの送信/受信、コントロールキャンバスへの描画、ユーザーインタラクションの認識などを意味します。このスレッドは、本質的に非常に速く回転する巨大なループです。この紡績スレッドのすべての革命について、特定のコードが実行されます。
単一のスレッド環境では、このファイルのダウンロードは、ダウンロードが完了するまで、このループを回転させます。この間、このスレッドはUIの更新を実行したり、ユーザーのクリックを検出したりすることができなくなりました。これは、Windowsがそのようなフォームのタイトルに(応答しない)を引き起こす原因です。
これは、追加のスレッドが入ってくる場所です。Windowsに応答する必要があります。この巨大なファイルのダウンロードでメインのUIスレッドをブロックする代わりに、そのファイルを別のスレッドに入れることができます。そんなに簡単ですよね?
「進捗状況を監視するにはどうすればよいですか?」と自問することができます。または「完了したら通知を受けるにはどうすればよいですか?」これは、ダウンロードスレッドが何らかの形でメインスレッドと対話する必要があることを意味します。これはまさに混乱が発生する場所です。1つのスレッドは、1つのスレッドが実際にどのポイントにあるかを示すことがないため、単に別のスレッドに干渉することはできません。現在、2つの個別のループがあり、UIを更新したい場合、そのUIスレッドはどこでも何でもできます。最も重要なことは、メインのUIスレッドが、他のスレッドも書きたいのと同じ制御プロパティに文字列を書き込むプロセスにあると仮定することです。これで、同じメモリアドレスに書き込もうとする2つのスレッドがあり、予測不可能な問題になる可能性があります。
同期することによって。 Delphiのtthreadクラスには、スレッドが実際に適切に動作する瞬間にのみ、スレッドが実際にそのような発生を予想しているときにのみメインと対話できるようにするメソッドSynchronize()があります。別のスレッドから同期されるコードは、実際にはそのスレッドのコンテキストで実行されません - メインUIスレッドのコンテキストで常に実行されます。これがSynchronize()のアイデアであり、UIスレッドでコードを実行することです。
そのため、最終的には、実際にはスレッドからVCLを使用しません。代わりに、スレッドはメインスレッドに信号を送信し、メインスレッドの準備ができた場合にのみ、そのコードを実行します。一方、メインスレッドが終了するのを待つ間、セカンダリスレッドがブロックされます。
次に、大規模なUI操作がスレッドでより良いと考えるという間違いがあります。何百万ものアイテムを入力したいリストがあるとしましょう。もちろん、それには時間がかかります。この間、アプリケーションは応答しません。また。それで、そのコードをスレッドに移動するだけですよね?
繰り返しますが、UIの相互作用はメインスレッドから、メインスレッドのみから行う必要があります。スレッドは、長い計算を実行したり、膨大な量のデータを処理したり、リモートリソースからの応答を待つ必要がある場合に役立ちます。
言うのは難しいです。しかし、スレッドを書くときに非常にアドバイスされる一般的な慣行があります。スレッドコードを独自のユニットに配置します。このユニットは、他のUIユニットから分離する必要があります。使用節にVCL関連ユニットさえあるはずです。スレッドは、どのように使用されているかさえ知らないはずです。それは本質的にダミーでなければならず、あなたの長いタスクを実行することを唯一の目的としています。スレッドからのUIの更新に関しては、これは同期イベントによって最もよく達成されます。
まさにそれがどのように聞こえますか。前に説明したように、これは同期されるイベントです。イベントは、スレッドが開始する前にスレッドに割り当てることができる手順への単なるポインターです。スレッド内では、UIを更新する必要がある場合、Synchronize()を使用してこのイベントをトリガーします。このデザインでは、スレッドはUIで使用されていることを知る必要はありません。同時に、あなたは誤って抽象化を達成します。スレッドは再利用可能になります。ユーザーインターフェイスさえない可能性のある他のプロジェクトに接続できます(Windowsサービスなど)。
検索したくない場合に備えて、VCLスレッドの安全性に関する関連リソースへの直接リンクを次に示します...
このアプリケーションは、Delphiでのスレッドの使用を示しています。知っておくべき多くの異なることがあるので、それらはさまざまな目的で異なるセクションに分かれています。各トピックには、少なくとも1つのフォームユニット(タブシートに埋め込まれています)、およびユーザーインターフェイスとは別に機能を含む少なくとも1つのスタンドアロンユニットがあります。これは意図的に行われ、スレッドが任意のUIから分離されるべきであることを示すために行われます。
メインフォーム自体には、実際にはロジックがありません。それがするのは、フォームをタブに埋め込むことだけです。 FormCreate()イベントハンドラーでは、各タブシートにフォームをインスタンス化するEmbedForm()に多数の呼び出しを行います。
実際にアプリケーションを使用することは非常に簡単です。タブの1つに移動するだけで、それぞれに独自の指示があります。


スレッド内のインターネットからファイルをダウンロードする方法を示します。ダウンロードを実行する単一のユニバーサル関数が定義されたDownloadFile()があります。 UIには3つのボタンがあります。
デフォルトでは、ダウンロードするURLはThinkBroadBand.comが提供するテストファイルですが、任意のURLを使用できます。ファイルを保存する場所を選択することもできます。これは非常にシンプルなデモであるため、ローカルファイル名のファイル名/拡張機能をニーズに合わせて調整する必要があります。ダウンロードしているURLに対して自動的に変更されません(ブラウザが通常行うように)。

長いタスクを実行しているスレッドから進行状況バーを更新する方法を示します。

スレッド内のデータベース接続を使用して、データをUIスレッドに同期することを示します。

大規模なCPUサイクルを消費する複数のスレッドを実証して、プロセッサをテストします。