Bagaimana cara memanggil metode asinkron dari metode sinkron dalam C #?

863

Saya memiliki public async void Foo()metode yang ingin saya panggil dari metode sinkron. Sejauh ini semua yang saya lihat dari dokumentasi MSDN adalah memanggil metode async melalui metode async, tetapi seluruh program saya tidak dibangun dengan metode async.

Apakah ini mungkin?

Berikut adalah salah satu contoh memanggil metode ini dari metode asinkron: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

Sekarang saya sedang mencari cara memanggil metode async ini dari metode sinkronisasi.

Menara
sumber
2
Saya juga mengalami hal ini. Mengganti RoleProvider Anda tidak bisa mengubah tanda tangan metode metode GetRolesForUser sehingga Anda tidak bisa membuat metode async dan karenanya tidak bisa menggunakan menunggu untuk memanggil api secara asinkron. Solusi sementara saya adalah menambahkan metode sinkron ke kelas HttpClient generik saya tetapi ingin tahu apakah ini mungkin (dan apa implikasinya).
Timothy Lee Russell
1
Karena async void Foo()metode Anda tidak mengembalikannya, Taskitu berarti seorang penelepon tidak dapat mengetahui kapan itu selesai, ia harus kembali Tasksebagai gantinya.
Dai
1
Menautkan q / a terkait tentang cara melakukan ini pada utas UI.
noseratio

Jawaban:

711

Pemrograman asinkron tidak "tumbuh" melalui basis kode. Ini telah dibandingkan dengan virus zombie . Solusi terbaik adalah membiarkannya tumbuh, tetapi terkadang itu tidak mungkin.

Saya telah menulis beberapa jenis di pustaka Nito.AsyncEx saya untuk berurusan dengan basis kode asinkron parsial. Tidak ada solusi yang berfungsi di setiap situasi.

Solusi A

Jika Anda memiliki metode asinkron sederhana yang tidak perlu disinkronkan kembali ke konteksnya, maka Anda dapat menggunakan Task.WaitAndUnwrapException:

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

Anda tidak ingin menggunakan Task.Waitatau Task.Resultkarena mereka memasukkan pengecualian AggregateException.

Solusi ini hanya sesuai jika MyAsyncMethodtidak disinkronkan kembali ke konteksnya. Dengan kata lain, setiap awaitdi MyAsyncMethodharus berakhir dengan ConfigureAwait(false). Ini berarti tidak dapat memperbarui elemen UI apa pun atau mengakses konteks permintaan ASP.NET.

Solusi B

Jika MyAsyncMethodmemang perlu disinkronkan kembali ke konteksnya, maka Anda mungkin dapat menggunakan AsyncContext.RunTaskuntuk memberikan konteks bersarang:

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* Pembaruan 4/14/2014: Dalam versi perpustakaan yang lebih baru, API adalah sebagai berikut:

var result = AsyncContext.Run(MyAsyncMethod);

(Tidak apa-apa untuk digunakan Task.Resultdalam contoh ini karena RunTaskakan menyebarkan Taskpengecualian).

Alasan Anda mungkin perlu AsyncContext.RunTaskbukannya Task.WaitAndUnwrapExceptionkarena kebuntuan yang agak halus yang terjadi pada WinForms / WPF / SL / ASP.NET:

  1. Metode sinkron memanggil metode async, memperoleh a Task.
  2. Metode sinkron melakukan pemblokiran menunggu di Internet Task.
  3. The asyncMetode menggunakan awaittanpa ConfigureAwait.
  4. Tidak Taskdapat menyelesaikan dalam situasi ini karena hanya selesai ketika asyncmetode selesai; yang asyncmetode dapat tidak lengkap karena mencoba untuk menjadwalkan kelanjutan kepada SynchronizationContext, dan WinForms / WPF / SL / ASP.NET tidak akan mengizinkan kelanjutan untuk menjalankan karena metode sinkron sudah berjalan dalam konteks itu.

Ini adalah salah satu alasan mengapa sebaiknya menggunakan sebanyak mungkin metode ConfigureAwait(false)dalam setiap asyncmetode.

Solusi C

AsyncContext.RunTasktidak akan berfungsi di setiap skenario. Misalnya, jika asyncmetode menunggu sesuatu yang memerlukan acara UI untuk menyelesaikan, maka Anda akan menemui jalan buntu bahkan dengan konteks bersarang. Jika demikian, Anda bisa memulai asyncmetode di kumpulan utas:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

Namun, solusi ini membutuhkan MyAsyncMethodyang akan bekerja dalam konteks thread pool. Jadi itu tidak dapat memperbarui elemen UI atau mengakses konteks permintaan ASP.NET. Dan dalam hal ini, Anda mungkin juga menambahkan ConfigureAwait(false)untuk nya awaitpernyataan, dan menggunakan solusi A.

Pembaruan, 2019-05-01: "Praktik terburuk" saat ini ada di artikel MSDN di sini .

Stephen Cleary
sumber
9
Solusi A sepertinya seperti yang saya inginkan, tetapi sepertinya task.WaitAndUnwrapException () tidak berhasil masuk ke .Net 4.5 RC; hanya memiliki tugas. Tunggu (). Adakah yang tahu bagaimana melakukan ini dengan versi yang baru? Atau apakah ini metode ekstensi khusus yang Anda tulis?
deadlydog
3
WaitAndUnwrapExceptionadalah metode saya sendiri dari pustaka AsyncEx saya . Lib .NET resmi tidak memberikan banyak bantuan untuk mencampur sinkronisasi dan kode async (dan secara umum, Anda seharusnya tidak melakukannya!). Saya menunggu .NET 4.5 RTW dan laptop non-XP baru sebelum memperbarui AsyncEx untuk berjalan pada 4,5 (saat ini saya tidak dapat mengembangkan untuk 4,5 karena saya terjebak di XP selama beberapa minggu lagi).
Stephen Cleary
12
AsyncContextsekarang memiliki Runmetode yang mengambil ekspresi lambda, jadi Anda harus menggunakanvar result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary
1
Saya mengeluarkan perpustakaan Anda dari Nuget, tetapi sepertinya tidak memiliki RunTaskmetode. Hal terdekat yang bisa saya temukan adalah Run, tetapi itu tidak memiliki Resultproperti.
Asad Saeeduddin
3
@Asad: Ya, lebih dari 2 tahun kemudian API telah berubah. Anda sekarang dapat dengan mudah mengatakanvar result = AsyncContext.Run(MyAsyncMethod);
Stephen Cleary
313

Menambahkan solusi yang akhirnya menyelesaikan masalah saya, semoga menghemat waktu seseorang.

Pertama baca beberapa artikel dari Stephen Cleary :

Dari "dua praktik terbaik" di "Jangan Blokir pada Kode Async", yang pertama tidak bekerja untuk saya dan yang kedua tidak berlaku (pada dasarnya jika saya bisa menggunakan await, saya lakukan!).

Jadi di sini adalah solusi saya: bungkus panggilan di dalam Task.Run<>(async () => await FunctionAsync());dan mudah-mudahan tidak ada jalan buntu lagi.

Ini kode saya:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}
Tohid
sumber
5
Dua tahun kemudian, saya ingin tahu bagaimana solusi ini bertahan. Ada berita? Apakah ada kehalusan pendekatan ini yang hilang pada pemula?
Dan Esparza
26
Ini tidak akan menemui jalan buntu, benar, tetapi hanya karena terpaksa dijalankan di utas baru, di luar konteks sinkronisasi utas asalnya. Namun, ada lingkungan tertentu di mana ini sangat keliru: terutama aplikasi web. Ini secara efektif dapat membagi dua utas yang tersedia untuk server web (satu utas untuk permintaan dan satu utas untuk ini). Semakin banyak Anda melakukan ini, semakin buruk hasilnya. Anda berpotensi berakhir dengan kebuntuan seluruh server web Anda.
Chris Pratt
30
@ ChrisPratt - Anda mungkin benar, karena Task.Run()ini bukan praktik terbaik dalam kode async. Tetapi, sekali lagi, apa jawaban untuk pertanyaan awal? Jangan pernah panggil metode async secara sinkron? Kami berharap, tetapi di dunia nyata, kadang-kadang kita harus melakukannya.
Tohid
1
@ Tomid, Anda bisa mencoba perpustakaan Stephen Cleary. Saya telah melihat orang-orang menganggap ini dan Parallel.ForEachpelecehan tidak akan berpengaruh pada 'dunia nyata' dan pada akhirnya ia menurunkan server. Kode ini OK untuk aplikasi Konsol tetapi seperti yang dikatakan @ChrisPratt, tidak boleh digunakan di Aplikasi Web. Mungkin berfungsi "sekarang" tetapi tidak dapat diskalakan.
makhdumi
1
Saya tertarik untuk mulai membuat akun baru di SO menjawab pertanyaan hanya untuk mendapatkan poin yang cukup untuk mengungguli yang satu ini ....
Giannis Paraskevopoulos
206

Microsoft membangun kelas AsyncHelper (internal) untuk menjalankan Async sebagai Sinkronisasi. Sumbernya terlihat seperti:

internal static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new 
      TaskFactory(CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return AsyncHelper._myTaskFactory
          .StartNew<Task<TResult>>(func)
          .Unwrap<TResult>()
          .GetAwaiter()
          .GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        AsyncHelper._myTaskFactory
          .StartNew<Task>(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    }
}

Kelas dasar Microsoft.AspNet.Identity hanya memiliki metode Async dan untuk memanggil mereka sebagai Sinkronisasi ada kelas dengan metode ekstensi yang terlihat seperti (contoh penggunaan):

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

Bagi mereka yang peduli tentang ketentuan kode lisensi, berikut ini tautan ke kode yang sangat mirip (hanya menambahkan dukungan untuk kultur di utas) yang memiliki komentar untuk menunjukkan bahwa itu adalah MIT yang dilisensikan oleh Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

Erik Philips
sumber
2
Metode async saya menunggu metode async lainnya. Saya TIDAK menghiasi salah satu awaitpanggilan saya dengan ConfigureAwait(false). Saya mencoba menggunakan AsyncHelper.RunSyncuntuk memanggil fungsi async dari Application_Start()fungsi di Global.asax dan sepertinya berfungsi. Apakah ini berarti bahwa AsyncHelper.RunSyncandal tidak rentan terhadap masalah kebuntuan "marshal kembali ke konteks pemanggil" yang saya baca di tempat lain dalam posting ini?
Bob.at.Indigo.Health
1
@ Bob.at.SBS tergantung pada apa yang Anda lakukan kode. Ini tidak sesederhana jika saya menggunakan kode ini saya aman . Ini adalah cara yang sangat minimal dan semi-aman untuk menjalankan perintah async secara serempak, dapat dengan mudah digunakan secara tidak tepat untuk menyebabkan kebuntuan.
Erik Philips
1
Terima kasih. 2 pertanyaan tindak lanjut: 1) Dapatkah Anda memberikan contoh sesuatu yang metode async ingin hindari yang akan menyebabkan kebuntuan, dan 2) kebuntuan dalam konteks ini sering tergantung waktu? Jika bekerja dalam praktek, mungkin saya masih memiliki kebuntuan tergantung waktu mengintai dalam kode saya?
Bob.at.Indigo.Health
@ Bob.at.SBS Saya akan merekomendasikan mengajukan pertanyaan dengan menggunakan tombol Tanya Pertanyaan di kanan atas. Anda dapat memasukkan tautan ke pertanyaan atau jawaban ini dalam pertanyaan Anda sebagai referensi.
Erik Philips
1
@ Bob.at ... kode yang disediakan oleh Erik berfungsi sempurna di bawah Asp. net MVC5 dan EF6, tetapi tidak ketika saya mencoba salah satu solusi lain (ConfigureAwait (false) .GetAwaiter (). GetResult () atau .result) yang menggantung sepenuhnya aplikasi web saya
LeonardoX
151

async Main sekarang merupakan bagian dari C # 7.2 dan dapat diaktifkan dalam pengaturan lanjutan bangunan proyek.

Untuk C # <7.2, cara yang benar adalah:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

Anda akan melihat ini digunakan dalam banyak dokumentasi Microsoft, misalnya: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- topik-langganan

Lee Smith
sumber
11
Saya tidak tahu MENGAPA seseorang memilih ini. Ini bekerja baik untuk saya. Tanpa perbaikan ini, saya harus menyebarkan ASYCH MANA SAJA.
Tahanan NOL
11
Mengapa ini lebih baik daripada MainAsync().Wait()?
naksir
8
Saya setuju. Anda hanya perlu MainAsync (). Tunggu () alih-alih semua ini.
Hajjat
8
@ Crush saya menjelaskan bagaimana ini dapat menghindari beberapa kebuntuan. Dalam beberapa situasi memanggil .Wait () dari utas UI atau asp.net menyebabkan jalan buntu. kebuntuan async
David
6
@ClintB: Anda benar-benar tidak boleh melakukan ini di ASP.NET Core. Aplikasi web sangat rentan untuk kekurangan benang, dan setiap kali Anda melakukan ini, Anda menarik utas dari kumpulan yang akan digunakan untuk melayani permintaan. Ini kurang bermasalah untuk aplikasi desktop / mobile karena mereka secara tradisional adalah pengguna tunggal.
Chris Pratt
52
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

Anda membaca kata kunci 'tunggu' sebagai "mulai tugas yang sudah berjalan lama ini, lalu kembalikan kontrol ke metode panggilan" Setelah tugas jangka panjang selesai, ia menjalankan kode setelahnya. Kode setelah menunggu mirip dengan apa yang dulu metode CallBack. Perbedaan besar menjadi aliran logis tidak terputus yang membuatnya lebih mudah untuk menulis dan membaca.

Despertar
sumber
15
Waitmembungkus pengecualian dan memiliki kemungkinan kebuntuan.
Stephen Cleary
Saya pikir jika Anda memanggil metode async tanpa menggunakan await, itu akan dieksekusi secara sinkron. Setidaknya itu bekerja untuk saya (tanpa menelepon myTask.Wait). Sebenarnya, saya mendapat pengecualian ketika saya mencoba menelepon myTask.RunSynchronously()karena sudah dieksekusi!
awe
2
Saya suka jawaban ini. Komentar bagus untuk pengeditan, kecil, dan elegan. Terima kasih telah berkontribusi! Saya masih belajar konkurensi, jadi semuanya membantu :)
kayleeFrye_onDeck
2
Haruskah jawaban ini masih berfungsi sampai hari ini? Saya baru saja mencobanya di proyek MVC Razor dan aplikasi hanya menggantung pada mengakses .Result.
Lewat Coding
8
@ TrueBlueAussie Itulah kebuntuan konteks sinkronisasi. Kode async Anda marshal kembali ke konteks sinkronisasi, tetapi itu diblokir oleh Resultpanggilan pada saat itu, sehingga tidak pernah sampai di sana. Dan Resulttidak pernah berakhir, karena menunggu seseorang yang menunggu untuk Resultberakhir, pada dasarnya: D
Luaan
40

Saya tidak 100% yakin, tapi saya percaya teknik yang dijelaskan dalam blog ini harus bekerja dalam banyak keadaan:

Dengan demikian Anda dapat menggunakan task.GetAwaiter().GetResult()jika Anda ingin secara langsung memanggil logika propagasi ini.

NStuke
sumber
6
Solusi A dalam jawaban Stephen Cleary di atas menggunakan metode ini. Lihat sumber WaitAndUnwrapException .
orad
apakah Anda perlu menggunakan GetResult () jika fungsi yang Anda panggil tidak valid atau tugas? Maksud saya jika Anda tidak ingin mendapatkan hasil apa pun kembali
batmaci
Ya, kalau tidak, itu tidak akan memblokir sampai tugas selesai. Atau alih-alih memanggil GetAwaiter (). GetResult () Anda dapat memanggil .Wait ()
NStuke
1
Itulah bagian "banyak situasi". Itu tergantung pada model threading keseluruhan dan apa yang dilakukan thread lain untuk menentukan apakah ada risiko kebuntuan atau tidak.
NStuke
GetAwaiter (). GetResult () masih dapat menyebabkan kebuntuan. Ini hanya membuka bungkus pengecualian menjadi yang lebih masuk akal.
nawfal
25

Namun, ada solusi bagus yang bekerja di (hampir: lihat komentar) setiap situasi: pompa pesan ad-hoc (SynchronizationContext).

Utas panggilan akan diblokir seperti yang diharapkan, sambil tetap memastikan bahwa semua kelanjutan yang dipanggil dari fungsi async tidak menemui jalan buntu karena mereka akan diarahkan ke Ad-hoc SynchronizationContext (pompa pesan) yang berjalan pada utas panggilan.

Kode pembantu pompa pesan ad-hoc:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

Pemakaian:

AsyncPump.Run(() => FooAsync(...));

Penjelasan lebih rinci tentang pompa async tersedia di sini .

Robert J
sumber
Konteks pengecualian dan AsyncPump stackoverflow.com/questions/23161693/...
PreguntonCojoneroCabrón
Ini tidak berfungsi dalam skenario Asp.net, karena Anda dapat kehilangan HttpContext.Current secara acak.
Josh Mouch
12

Kepada siapa pun yang memperhatikan pertanyaan ini lagi ...

Jika Anda melihat Microsoft.VisualStudio.Services.WebApiada kelas yang disebut TaskExtensions. Di dalam kelas itu Anda akan melihat metode ekstensi statis Task.SyncResult(), yang seperti benar-benar hanya memblokir utas sampai tugas kembali.

Secara internal itu panggilan task.GetAwaiter().GetResult()yang sangat sederhana, namun itu kelebihan beban untuk bekerja pada asyncmetode apa pun yang kembali Task, Task<T>atau Task<HttpResponseMessage>... gula sintaksis, sayang ... ayah punya gigi manis.

Sepertinya ...GetAwaiter().GetResult()ini adalah cara resmi-MS untuk mengeksekusi kode async dalam konteks pemblokiran. Tampaknya bekerja dengan sangat baik untuk use case saya.

jrypkahauer
sumber
3
Anda memiliki saya di "seperti benar-benar hanya blok".
Dawood ibn Kareem
9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

Atau gunakan ini:

var result=result.GetAwaiter().GetResult().AccessToken
rajesh A
sumber
6

Anda dapat memanggil metode asinkron apa pun dari kode sinkron, yaitu, sampai Anda perlu awaitmenggunakannya, dalam hal ini mereka juga harus ditandai async.

Karena banyak orang menyarankan di sini, Anda bisa memanggil Tunggu () atau Hasil pada tugas yang dihasilkan dalam metode sinkron Anda, tetapi kemudian Anda berakhir dengan panggilan pemblokiran dalam metode itu, yang jenisnya mengalahkan tujuan async.

Jika Anda benar-benar tidak dapat membuat metode asyncAnda dan Anda tidak ingin mengunci metode sinkron, maka Anda harus menggunakan metode panggilan balik dengan meneruskannya sebagai parameter ke metode ContinueWith pada tugas.

base2
sumber
5
Maka itu tidak akan memanggil metode secara sinkron sekarang kan?
Jeff Mercado
2
Seperti yang saya mengerti, pertanyaannya adalah Anda dapat memanggil metode async dari metode non-async. Ini tidak berarti harus memanggil metode async dengan cara memblokir.
base2
Maaf, "mereka harus ditandai asyncjuga" menarik perhatian saya dari apa yang sebenarnya Anda katakan.
Jeff Mercado
Jika saya benar-benar tidak peduli dengan ketidaksinkronan, apakah boleh saya menyebutnya demikian (dan bagaimana dengan kemungkinan deadlock dalam pengecualian yang dibungkus Stephen Cleary?) Saya memiliki beberapa metode pengujian (yang harus dijalankan secara serempak) yang menguji metode asinkron. Saya harus menunggu hasilnya sebelum melanjutkan, sehingga saya dapat menguji hasil dari metode asinkron.
awe
6

Saya tahu saya sangat terlambat. Tapi kalau-kalau seseorang seperti saya ingin menyelesaikan ini dengan rapi, cara mudah, dan tanpa tergantung pada perpustakaan lain.

Saya menemukan potongan kode berikut dari Ryan

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

maka Anda bisa menyebutnya seperti ini

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
Wahid Bitar
sumber
6
Ini persis seperti jawaban di atas apakah saya kehilangan sesuatu
inlokesh
2

Setelah berjam-jam mencoba metode yang berbeda, dengan keberhasilan yang kurang lebih, inilah yang saya akhiri. Itu tidak berakhir dengan jalan buntu saat mendapatkan hasil dan juga mendapatkan dan melempar pengecualian asli dan bukan yang dibungkus.

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}
Jiří Herník
sumber
1
Bekerja dengan mengembalikan tugas. GetAwaiter (). GetResult ();
Per G
ya, tapi bagaimana dengan pengecualian aslinya?
Jiří Herník
.Hasil saya pikir pada dasarnya sama dengan .GetAwaiter (). GetResult ()
Per G
-2

Itu bisa dipanggil dari utas baru (BUKAN dari kumpulan utas!):

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );
Garm
sumber
-3

Metode windows async tersebut memiliki metode kecil yang bagus yang disebut AsTask (). Anda bisa menggunakan ini untuk meminta metode mengembalikan dirinya sebagai tugas sehingga Anda bisa memanggil Tunggu () secara manual.

Misalnya, pada aplikasi Windows Phone 8 Silverlight, Anda dapat melakukan hal berikut:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

Semoga ini membantu!

Licik
sumber
-4

Jika Anda ingin menjalankannya Sync

MethodAsync().RunSynchronously()
smj
sumber
3
Metode ini dimaksudkan untuk memulai tugas dingin. Biasanya metode async mengembalikan tugas panas, dengan kata lain tugas yang sudah dimulai. memanggil RunSynchronously()hasil tugas panas ke sebuah InvalidOperationException. Cobalah dengan kode ini:Task.Run(() => {}).RunSynchronously();
Theodor Zoulias
-5
   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;
Arvind Kumar Chaodhary
sumber
2
Hasilkan kebuntuan. Lebih baik hapus jawabannya.
PreguntonCojoneroCabrón
Task.Run (() => SaveAssetDataAsDraft ()) Hasil; - tidak menghasilkan kebuntuan
Anubis