Pemrograman asinkron dengan Asinkron dan Await (Visual Basic)

Anda dapat menghindari penyempitan performa dan meningkatkan respons keseluruhan aplikasi Anda dengan menggunakan pemrograman asinkron. Namun, teknik tradisional untuk menulis aplikasi asinkron bisa rumit, membuatnya sulit ditulis, di-debug, dan dipertahankan.

Visual Studio 2012 memperkenalkan pendekatan yang disederhanakan, pemrograman asinkron, yang memanfaatkan dukungan asinkron di .NET Framework 4.5 dan yang lebih tinggi serta di Windows Runtime. Pengompilasi melakukan pekerjaan sulit yang biasa dilakukan pengembang, dan aplikasi Anda mempertahankan struktur logis yang menyerupai kode sinkron. Akibatnya, Anda mendapatkan semua keuntungan dari pemrograman asinkron dengan sebagian kecil dari upaya.

Topik ini memberikan gambaran umum tentang kapan dan bagaimana menggunakan pemrograman asinkron dan menyertakan tautan ke topik dukungan yang berisi detail dan contoh.

Asinkron meningkatkan responsivitas

Asinkron sangat penting untuk aktivitas yang berpotensi memblokir, seperti ketika aplikasi Anda mengakses web. Akses ke sumber daya web terkadang lambat atau tertunda. Jika aktivitas seperti itu diblokir dalam proses sinkron, seluruh aplikasi harus menunggu. Dalam proses asinkron, aplikasi dapat melanjutkan pekerjaan lain yang tidak bergantung pada sumber daya web hingga tugas yang berpotensi memblokir selesai.

Tabel berikut ini memperlihatkan area umum di mana pemrograman asinkron meningkatkan daya respons. API yang tercantum dari .NET Framework 4.5 dan Windows Runtime berisi metode yang mendukung pemrograman asinkron.

Area aplikasi API pendukung yang berisi metode asinkron
Akses web HttpClient, SyndicationClient
Bekerja dengan file StorageFile, StreamWriter, StreamReader, XmlReader
Bekerja dengan gambar MediaCapture, BitmapEncoder, BitmapDecoder
Pemrograman WCF Operasi Sinkron dan Asinkron

Asinkron terbukti sangat berharga untuk aplikasi yang mengakses utas UI karena semua aktivitas terkait UI biasanya berbagi satu utas. Jika ada proses yang diblokir dalam aplikasi sinkron, semua diblokir. Aplikasi Anda berhenti merespons, dan Anda mungkin menyimpulkan bahwa aplikasi gagal ketika hanya menunggu.

Ketika Anda menggunakan metode asinkron, aplikasi terus merespons UI. Anda dapat mengubah ukuran atau meminimalkan jendela, misalnya, atau Anda dapat menutup aplikasi jika Anda tidak ingin menunggunya selesai.

Pendekatan berbasis asinkron menambahkan transmisi otomatis yang setara dengan daftar opsi yang dapat Anda pilih saat merancang operasi asinkron. Artinya, Anda mendapatkan semua manfaat pemrograman asinkron tradisional tetapi dengan upaya yang jauh lebih sedikit dari pengembang.

Metode asinkron lebih mudah ditulis

Kata kunci Asinkron dan Await di Visual Basic adalah jantung dari pemrograman asinkron. Dengan menggunakan kedua kata kunci tersebut, Anda dapat menggunakan sumber daya di .NET Framework atau runtime Windows untuk membuat metode asinkron hampir semampu Anda membuat metode sinkron. Metode asinkron yang Anda tentukan dengan menggunakan Async dan Await disebut sebagai metode asinkron.

Contoh berikut menunjukkan metode asinkron. Hampir semua yang ada dalam kode akan terlihat sangat familier untuk Anda. Komentar memanggil fitur yang Anda tambahkan untuk membuat asinkron.

Anda dapat menemukan file contoh Windows Presentation Foundation (WPF) lengkap di akhir topik ini, dan Anda dapat mengunduh sampel dari Sampel Asinkron: Contoh dari "Pemrograman Asinkron dengan Asinkron dan Await".

' Three things to note about writing an Async Function:
'  - The function has an Async modifier.
'  - Its return type is Task or Task(Of T). (See "Return Types" section.)
'  - As a matter of convention, its name ends in "Async".
Async Function AccessTheWebAsync() As Task(Of Integer)
    Using client As New HttpClient()
        ' Call and await separately.
        '  - AccessTheWebAsync can do other things while GetStringAsync is also running.
        '  - getStringTask stores the task we get from the call to GetStringAsync.
        '  - Task(Of String) means it is a task which returns a String when it is done.
        Dim getStringTask As Task(Of String) =
            client.GetStringAsync("https://docs.microsoft.com/dotnet")
        ' You can do other work here that doesn't rely on the string from GetStringAsync.
        DoIndependentWork()
        ' The Await operator suspends AccessTheWebAsync.
        '  - AccessTheWebAsync does not continue until getStringTask is complete.
        '  - Meanwhile, control returns to the caller of AccessTheWebAsync.
        '  - Control resumes here when getStringTask is complete.
        '  - The Await operator then retrieves the String result from getStringTask.
        Dim urlContents As String = Await getStringTask
        ' The Return statement specifies an Integer result.
        ' A method which awaits AccessTheWebAsync receives the Length value.
        Return urlContents.Length

    End Using

End Function

Jika AccessTheWebAsync tidak memiliki pekerjaan apa pun yang dapat dilakukan antara memanggil GetStringAsync dan menunggu penyelesaiannya, Anda dapat menyederhanakan kode Anda dengan memanggil dan menunggu dalam pernyataan tunggal berikut.

Dim urlContents As String = Await client.GetStringAsync()

Karakteristik berikut meringkas apa yang menjadikan contoh sebelumnya sebagai metode asinkron:

  • Tanda tangan metode mencakup pengubah Async.

  • Nama metode asinkron, menurut konvensi, diakhiri dengan akhiran "Asinkron".

  • Jenis pengembalian adalah salah satu jenis berikut:

    • Task(Of TResult) jika metode Anda memiliki pernyataan pengembalian di mana operand memiliki jenis TResult.
    • Task jika metode Anda tidak memiliki pernyataan pengembalian atau memiliki pernyataan pengembalian tanpa operand.
    • Sub jika Anda menulis penanganan aktivitas asinkron.

    Untuk informasi selengkapnya, lihat "Kembalikan Jenis dan Parameter" nanti dalam topik ini.

  • Metode ini biasanya mencakup setidaknya satu ekspresi tunggu, yang menandai titik di mana metode tidak dapat dilanjutkan sampai operasi asinkron yang ditunggu selesai. Sementara itu, metode ditangguhkan, dan kontrol kembali ke pemanggil metode. Bagian berikutnya dari topik ini menggambarkan apa yang terjadi pada titik penangguhan.

Dalam metode asinkron, Anda menggunakan kata kunci dan jenis yang disediakan untuk menunjukkan apa yang ingin Anda lakukan, dan pengkompilasi melakukan sisanya, termasuk melacak apa yang harus terjadi ketika kontrol kembali ke titik tunggu dalam metode yang ditangguhkan. Beberapa proses rutin, seperti perulangan dan penanganan pengecualian, mungkin sulit ditangani dalam kode asinkron tradisional. Dalam metode asinkron, Anda menulis elemen-elemen ini sebanyak yang Anda lakukan dalam solusi sinkron, dan masalahnya diselesaikan.

Untuk informasi selengkapnya tentang asinkron di versi .NET Framework sebelumnya, lihat TPL dan Pemrograman Asinkron .NET Framework Tradisional.

Apa yang terjadi dalam metode Asinkron

Hal yang paling penting untuk dipahami dalam pemrograman asinkron adalah bagaimana alur kontrol berpindah dari metode ke metode. Diagram berikut mengarahkan Anda melalui proses:

Diagram that shows tracing an async program.

Angka-angka dalam diagram sesuai dengan aliran data berikut:

  1. Penanganan aktivitas memanggil dan menunggu AccessTheWebAsync metode asinkron.

  2. AccessTheWebAsync membuat instans HttpClient dan memanggil metode asinkron GetStringAsync untuk mengunduh konten situs web sebagai string.

  3. Sesuatu terjadi di GetStringAsync yang menangguhkan kemajuannya. Mungkin harus menunggu situs web diunduh atau aktivitas pemblokiran lainnya. Untuk menghindari pemblokiran sumber daya, GetStringAsync menghasilkan kontrol kepada pemanggilnya, AccessTheWebAsync.

    GetStringAsync mengembalikan Task(Of TResult) di mana TResult adalah string, dan AccessTheWebAsync menetapkan tugas ke variabel getStringTask. Tugas ini mewakili proses yang sedang berlangsung untuk panggilan ke GetStringAsync, dengan komitmen untuk menghasilkan nilai string aktual ketika pekerjaan selesai.

  4. Karena getStringTask belum ditunggu, AccessTheWebAsync dapat dilanjutkan dengan pekerjaan lain yang tidak bergantung pada hasil akhir dari GetStringAsync. Pekerjaan itu diwakili oleh panggilan ke metode sinkron DoIndependentWork.

  5. DoIndependentWork adalah metode sinkron yang melakukan pekerjaannya dan kembali ke pemanggilnya.

  6. AccessTheWebAsync telah kehabisan pekerjaan yang dapat dilakukan tanpa hasil dari getStringTask. AccessTheWebAsync berikutnya ingin menghitung dan mengembalikan panjang string yang diunduh, tetapi metode tidak dapat menghitung nilai tersebut sampai metode memiliki string.

    Oleh karena itu, AccessTheWebAsync menggunakan operator tunggu untuk menangguhkan kemajuannya dan menghasilkan kontrol ke metode yang disebut AccessTheWebAsync. AccessTheWebAsync mngembalikan Task(Of Integer) ke penelepon. Tugas ini mewakili janji untuk menghasilkan hasil bilangan bulat yang panjang string yang diunduh.

    Catatan

    Jika GetStringAsync (dan oleh karena itu getStringTask) selesai sebelum AccessTheWebAsync menunggunya, kontrol tetap berada di AccessTheWebAsync. Pengeluaran penangguhan dan kemudian kembali ke AccessTheWebAsync akan sia-sia jika proses asinkron yang disebut (getStringTask) telah selesai dan AccessTheWebSync tidak perlu menunggu hasil akhir.

    Di dalam pemanggil (penanganan aktivitas dalam contoh ini), pola pemrosesan berlanjut. Penelepon mungkin melakukan pekerjaan lain yang tidak bergantung pada hasil dari AccessTheWebAsync sebelum menunggu hasil tersebut, atau pemanggil mungkin segera menunggu. Penanganan aktivitas sedang menunggu AccessTheWebAsync, dan AccessTheWebAsync sedang menunggu GetStringAsync.

  7. GetStringAsync menyelesaikan dan menghasilkan hasil string. Hasil string tidak dikembalikan oleh panggilan ke GetStringAsync dengan cara yang mungkin Anda harapkan. (Ingat bahwa metode sudah mengembalikan tugas di langkah 3.) Sebaliknya, hasil string disimpan dalam tugas yang mewakili penyelesaian metode, getStringTask. Operator menunggu mengambil hasil dari getStringTask. Pernyataan penugasan menetapkan hasil yang diambil ke urlContents.

  8. Ketika AccessTheWebAsync memiliki hasil string, metode dapat menghitung panjang string. Kemudian pekerjaan AccessTheWebAsync juga selesai, dan penanganan aktivitas yang menunggu dapat dilanjutkan. Dalam contoh lengkap di akhir topik, Anda dapat mengonfirmasi bahwa penanganan aktivitas mengambil dan mencetak nilai hasil panjang.

Jika Anda baru menggunakan pemrograman asinkron, luangkan waktu satu menit untuk mempertimbangkan perbedaan antara perilaku sinkron dan asinkron. Metode sinkron mengembalikan ketika pekerjaannya selesai (langkah 5), tetapi metode asinkron mengembalikan nilai tugas ketika pekerjaannya ditangguhkan (langkah 3 dan 6). Ketika metode asinkron akhirnya menyelesaikan pekerjaannya, tugas ditandai sebagai selesai dan hasilnya, jika ada, disimpan dalam tugas.

Untuk informasi selengkapnya tentang alur kontrol, lihat Mengontrol Flow di Program Asinkron (Visual Basic).

Metode Asinkron API

Anda mungkin bertanya-tanya di mana menemukan metode seperti GetStringAsync yang mendukung pemrograman asinkron. .NET Framework 4.5 atau lebih tinggi berisi banyak anggota yang bekerja dengan Async dan Await. Anda dapat mengenali anggota ini dengan akhiran "Asinkron" yang dilampirkan ke nama anggota dan jenis pengembalian Tugas Task atau (Dari TResult). Misalnya, kelas System.IO.Stream berisi metode seperti CopyToAsync, ReadAsync, dan WriteAsync bersama metode sinkron CopyTo, Read, dan Write.

Windows Runtime juga berisi banyak metode yang dapat Anda gunakan dengan Async dan Await di aplikasi Windows. Untuk informasi selengkapnya dan contoh metode, lihat Memanggil API asinkron di C# atau Visual Basic, Pemrograman asinkron (aplikasi Windows Runtime), dan WhenAny: Menjembatani antara .NET Framework dan Runtime Windows.

Threads

Metode asinkron dimaksudkan untuk menjadi operasi non-pemblokiran. Ekspresi Await dalam metode asinkron tidak memblokir utas saat ini saat tugas yang ditunggu sedang berjalan. Sebaliknya, ekspresi mendaftarkan sisa metode sebagai kelanjutan dan mengembalikan kontrol ke pemanggil metode asinkron.

Kata kunci Async dan Await tidak menyebabkan utas tambahan dibuat. Metode asinkron tidak memerlukan multi-utas karena metode asinkron tidak berjalan pada utasnya sendiri. Metode ini berjalan pada konteks sinkronisasi saat ini dan menggunakan waktu pada utas hanya ketika metode aktif. Anda dapat menggunakan Task.Run untuk memindahkan pekerjaan yang terikat CPU ke utas latar belakang, tetapi utas latar belakang tidak membantu proses yang hanya menunggu hasil tersedia.

Pendekatan berbasis asinkron untuk pemrograman asinkron lebih disukai untuk pendekatan yang ada di hampir setiap kasus. Secara khusus, pendekatan ini lebih baik daripada BackgroundWorker untuk operasi terikat I/O karena kodenya lebih sederhana dan Anda tidak perlu menjaga kondisi balapan. Dalam kombinasi dengan Task.Run, pemrograman asinkron lebih baik daripada BackgroundWorker untuk operasi terikat CPU karena pemrograman asinkron memisahkan detail koordinasi menjalankan kode Anda dari pekerjaan yang ditransfer Task.Run ke threadpool.

Asinkron dan Await

Jika Anda menentukan bahwa metode adalah metode asinkron dengan menggunakan pengubah Asinkron, Anda mengaktifkan dua kemampuan berikut.

  • Metode asinkron yang ditandai dapat menggunakan Await untuk menunjuk titik suspensi. Operator menunggu memberi tahu pengompilasi bahwa metode asinkron tidak dapat melanjutkan melewati titik itu sampai proses asinkron yang ditunggu selesai. Sementara itu, kontrol kembali ke pemanggil metode asinkron.

    Penangguhan metode asinkron pada ekspresi Await tidak merupakan keluar dari metode, dan blok Finally tidak berjalan.

  • Metode asinkron yang ditandai sendiri dapat ditunggu oleh metode yang menyebutnya.

Metode asinkron biasanya berisi satu atau beberapa kemunculan operator Await, tetapi tidak adanya ekspresi Await tidak menyebabkan kesalahan pengkompilasi. Jika metode asinkron tidak menggunakan operator Await untuk menandai titik suspensi, metode dijalankan sebagai metode sinkron, meskipun ada pengubah Async. Kompilator mengeluarkan peringatan untuk metode tersebut.

Async dan Await merupakan kata kunci kontekstual. Untuk informasi dan contoh selengkapnya, lihat sumber daya berikut ini:

Mengembalikan jenis dan parameter

Dalam pemrograman .NET Framework, metode asinkron biasanya mengembalikan Task atau Task(Of TResult). Di dalam metode asinkron, operator Await diterapkan ke tugas yang dikembalikan dari panggilan ke metode asinkron lain.

Anda menentukan Task(Of TResult) sebagai jenis pengembalian jika metode berisi pernyataan Return yang menentukan operand jenis TResult.

Anda menggunakan Task sebagai jenis pengembalian jika metode tidak memiliki pernyataan pengembalian atau memiliki pernyataan pengembalian yang tidak mengembalikan operand.

Contoh berikut menunjukkan cara Anda mendeklarasikan dan memanggil metode yang mengembalikan Task(Of TResult) atau Task:

' Signature specifies Task(Of Integer)
Async Function TaskOfTResult_MethodAsync() As Task(Of Integer)

    Dim hours As Integer
    ' . . .
    ' Return statement specifies an integer result.
    Return hours
End Function

' Calls to TaskOfTResult_MethodAsync
Dim returnedTaskTResult As Task(Of Integer) = TaskOfTResult_MethodAsync()
Dim intResult As Integer = Await returnedTaskTResult
' or, in a single statement
Dim intResult As Integer = Await TaskOfTResult_MethodAsync()

' Signature specifies Task
Async Function Task_MethodAsync() As Task

    ' . . .
    ' The method has no return statement.
End Function

' Calls to Task_MethodAsync
Task returnedTask = Task_MethodAsync()
Await returnedTask
' or, in a single statement
Await Task_MethodAsync()

Setiap tugas yang dikembalikan mewakili pekerjaan yang sedang berlangsung. Tugas merangkum informasi tentang status proses asinkron dan, akhirnya, baik hasil akhir dari proses atau pengecualian yang dimunculkan proses jika tidak berhasil.

Metode asinkron juga dapat menjadi metode Sub. Jenis pengembalian ini digunakan terutama untuk menentukan penanganan aktivitas, di mana jenis pengembalian diperlukan. Penanganan aktivitas asinkron sering berfungsi sebagai titik awal untuk program asinkron.

Metode asinkron yang merupakan prosedur Sub tidak dapat ditunggu, dan pemanggil tidak dapat menangkap pengecualian apa pun yang dilemparkan metode.

Metode asinkron tidak dapat mendeklarasikan parameter ByRef, tetapi metode ini dapat memanggil metode yang memiliki parameter tersebut.

Untuk informasi dan contoh selengkapnya, lihat Jenis Pengembalian Asinkron (Visual Basic). Untuk informasi selengkapnya tentang cara menangkap pengecualian dalam metode asinkron, lihat Coba... Menangkap... Pernyataan Terakhir.

API asinkron dalam pemrograman runtime Windows memiliki salah satu jenis pengembalian berikut, yang mirip dengan tugas:

Untuk informasi selengkapnya dan contohnya, lihat Memanggil API asinkron di C# atau Visual Basic.

Konvensi penamaan

Berdasarkan konvensi, Anda menambahkan "Asinkron" ke nama metode yang memiliki pengubah Async.

Anda dapat mengabaikan konvensi di mana peristiwa, kelas dasar, atau kontrak antarmuka menyarankan nama yang berbeda. Misalnya, Anda tidak boleh mengganti nama penanganan aktivitas umum, seperti Button1_Click.

Topik dan sampel terkait (Visual Studio)

Judul Deskripsi Sampel
Panduan: Mengakses Web dengan Menggunakan Asinkron dan Await (Visual Basic) Memperlihatkan cara mengonversi solusi WPF sinkron ke solusi WPF asinkron. Aplikasi ini mengunduh serangkaian situs web. Sampel Asinkron: Pemrograman asinkron dengan Asinkron dan Await (Visual Basic)
Cara: Memperluas Panduan Asinkron dengan Menggunakan Task.WhenAll (Visual Basic) Menambahkan Task.WhenAll ke panduan sebelumnya. Penggunaan WhenAll memulai semua unduhan secara bersamaan.
Cara: Membuat Beberapa Permintaan Web Secara Paralel dengan Menggunakan Asinkron dan Await (Visual Basic) Menunjukkan cara memulai beberapa tugas secara bersamaan. Sampel Asinkron: Membuat Beberapa Permintaan Web secara Paralel
Jenis Pengembalian Asinkron (Visual Basic) Mengilustrasikan jenis yang dapat dikembalikan oleh metode asinkron dan menjelaskan kapan setiap jenis sesuai.
Mengontrol Flow dalam Program Asinkron (Visual Basic) Melacak secara rinci alur kontrol melalui keberhasilan ekspresi menunggu dalam program asinkron. Sampel Asinkron: Mengontrol Flow dalam Program Asinkron
Melakukan Fine-Tuning Pada Aplikasi Asinkron Anda (Visual Basic) Memperlihatkan cara menambahkan fungsionalitas berikut ke solusi asinkron Anda:

- Membatalkan Tugas Asinkron atau Daftar Tugas (Visual Basic)
- Membatalkan Tugas Asinkron setelah Periode Waktu tertentu (Visual Basic)
- Membatalkan Tugas Asinkron yang Tersisa setelah Satu Selesai (Visual Basic)
- Mulai Beberapa Tugas Asinkron dan Proses Saat Selesai (Visual Basic)
Sampel Asinkron: Menyempurnakan Aplikasi Anda
Menangani Reentrancy di Aplikasi Asinkron (Visual Basic) Memperlihatkan cara menangani kasus di mana operasi asinkron aktif dimulai ulang saat sedang berjalan.
WhenAny: Menjembatani antara .NET Framework dan Runtime Windows Memperlihatkan cara menjembatani antara jenis Tugas di .NET Framework dan IAsyncOperations di Runtime Windows sehingga Anda bisa menggunakan WhenAny dengan metode Runtime Windows. Sampel Asinkron: Menjembatani antara .NET dan Windows Runtime (AsTask dan WhenAny)
Pembatalan Asinkron: Menjembatani antara .NET Framework dan Runtime Windows Memperlihatkan cara menjembatani antara jenis Tugas di .NET Framework dan IAsyncOperations di Runtime Windows sehingga Anda bisa menggunakan CancellationTokenSource dengan metode Runtime Windows. Sampel Asinkron: Menjembatani antara .NET dan Windows Runtime (Pembatalan & AsTask)
Menggunakan Asinkron untuk Akses File (Visual Basic) Mencantumkan dan menunjukkan manfaat menggunakan asinkron dan menunggu untuk mengakses file.
Pola Asinkron Berbasis Tugas (TAP) Menjelaskan pola baru untuk asinkron dalam .NET Framework. Pola ini didasarkan pada jenis Task dan Tugas(Dari TResult).
Video Asinkron di Channel 9 Menyediakan tautan ke berbagai video tentang pemrograman asinkron.

Contoh lengkap

Kode berikut adalah file MainWindow.xaml.vb dari aplikasi Windows Presentation Foundation (WPF) yang dibahas dalam topik ini. Anda dapat mengunduh sampel dari Sampel Asinkron: Contoh dari "Pemrograman Asinkron dengan Asinkron dan Await".


Imports System.Net.Http

' Example that demonstrates Asynchronous Progamming with Async and Await.
' It uses HttpClient.GetStringAsync to download the contents of a website.
' Sample Output:
' Working . . . . . . .
'
' Length of the downloaded string: 39678.

Class MainWindow

    ' Mark the event handler with Async so you can use Await in it.
    Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)

        ' Call and await immediately.
        ' StartButton_Click suspends until AccessTheWebAsync is done.
        Dim contentLength As Integer = Await AccessTheWebAsync()

        ResultsTextBox.Text &= $"{vbCrLf}Length of the downloaded string: {contentLength}.{vbCrLf}"

    End Sub


    ' Three things to note about writing an Async Function:
    '  - The function has an Async modifier. 
    '  - Its return type is Task or Task(Of T). (See "Return Types" section.)
    '  - As a matter of convention, its name ends in "Async".
    Async Function AccessTheWebAsync() As Task(Of Integer)

        Using client As New HttpClient()

            ' Call and await separately. 
            '  - AccessTheWebAsync can do other things while GetStringAsync is also running.
            '  - getStringTask stores the task we get from the call to GetStringAsync. 
            '  - Task(Of String) means it is a task which returns a String when it is done.
            Dim getStringTask As Task(Of String) =
                client.GetStringAsync("https://docs.microsoft.com/dotnet")

            ' You can do other work here that doesn't rely on the string from GetStringAsync. 
            DoIndependentWork()

            ' The Await operator suspends AccessTheWebAsync.
            '  - AccessTheWebAsync does not continue until getStringTask is complete.
            '  - Meanwhile, control returns to the caller of AccessTheWebAsync.
            '  - Control resumes here when getStringTask is complete.
            '  - The Await operator then retrieves the String result from getStringTask.
            Dim urlContents As String = Await getStringTask

            ' The Return statement specifies an Integer result.
            ' A method which awaits AccessTheWebAsync receives the Length value.
            Return urlContents.Length

        End Using

    End Function

    Sub DoIndependentWork()
        ResultsTextBox.Text &= $"Working . . . . . . .{vbCrLf}"
    End Sub

End Class

Lihat juga