[英]Executing async function synchronously
我已經對這個主題進行了大量搜索,並且我閱讀了本網站上關於這個主題的大部分帖子,但是我仍然感到困惑,我需要一個直接的答案。 這是我的情況:
我有一個已建立的 Winform 應用程序,我不能讓它全部“異步”。 我現在被迫使用一個全部編寫為異步函數的外部庫。
在我的應用程序中,我有
/// <summary>
/// This function I can't change it to an 'async'
/// </summary>
public void MySyncFunction()
{
//This function is my point in my application where I have to call the
//other 'async' functions but I can't change the function itself to 'async'
try
{
//I need to call the MyAsyncDriverFunction() as if it is a synchronous function
//I need the driver function to finish execution and return before processing the code that follows it
//I also need to be able to catch any exceptions
MyAsyncDriverFunction();
//Rest of the code have to wait for the above function to return
}
catch (Exception exp)
{
//Need to be able to handle the exception thrown
//from the MyAsyncDriverFunction here.
}
}
public static async Task<IEnumerable<string>> MyAsyncDriverFunction()
{
try
{
var strCollection = await AsyncExternalLibraryFunction1();
var strCollection2 = await AsyncExternalLibraryFunction2();
return strCollection;
}
catch (Exception exp)
{
//Need to be able to catch an exception and re-throw it to the caller function
}
}
如代碼中所述,我需要能夠:
但是我仍然很困惑,我需要一個直接的答案。
那是因為沒有“直截了當”的答案。
唯一合適的解決方案是使MySyncFunction
異步。 時期。 所有其他解決方案都是 hack,並且沒有在所有場景中都能完美運行的 hack 。
我在最近的 MSDN 文章中詳細介紹了棕地異步開發,但要點如下:
您可以使用Wait()
或Result
進行阻塞。 正如其他人所指出的,您很容易導致死鎖,但如果異步代碼永遠不會在其捕獲的上下文中恢復,則這可以起作用。
您可以將工作推送到線程池線程,然后阻塞。 然而,這假設異步工作能夠被推送到某個其他任意線程,並且它可以在其他線程上恢復,因此可能會引入多線程。
您可以將工作推送到執行“主循環”的線程池線程 - 例如,調度程序或我自己的AsyncContext
類型。 這假設異步工作能夠被推送到另一個線程,但消除了對多線程的任何擔憂。
您可以在主線程上安裝嵌套消息循環。 這將在調用線程上執行異步代碼,但也會引入可重入性,這是非常難以正確推理的。
簡而言之,沒有一個答案。 每一種方法都是一種適用於不同類型異步代碼的技巧。
簡單地針對異步方法調用.Result
或.Wait
將會死鎖,因為您處於 GUI 應用程序的上下文中。 請參閱https://msdn.microsoft.com/en-us/magazine/jj991977.aspx (“一路異步”一章)以獲得很好的解釋。
解決您的問題並不容易,但 Stephen Cleary 已對其進行了詳細描述:這里。
所以你應該使用 Nito.AsyncEx 庫(在Nuget上可用)。 如果您真的無法將他編寫的庫添加到您的項目中,您可以檢查源代碼並使用其中的一部分,MIT 許可證允許這樣做。
只需在方法調用的末尾添加一個.Result
調用。
var strCollection = MyAsyncDriverFunction().Result;
我不確定專家會說什么,但根據Stephen Cleary 的建議,我最終得出了以下想法。 有以下課程
public sealed class AsyncTask
{
public static void Run(Func<Task> asyncFunc)
{
var originalContext = SynchronizationContext.Current;
bool restoreContext = false;
try
{
if (originalContext != null && originalContext.GetType() != typeof(SynchronizationContext))
{
restoreContext = true;
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
var task = asyncFunc();
task.GetAwaiter().GetResult();
}
finally
{
if (restoreContext) SynchronizationContext.SetSynchronizationContext(originalContext);
}
}
public static TResult Run<TResult>(Func<Task<TResult>> asyncFunc)
{
var originalContext = SynchronizationContext.Current;
bool restoreContext = false;
try
{
if (originalContext != null && originalContext.GetType() != typeof(SynchronizationContext))
{
restoreContext = true;
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
var task = asyncFunc();
return task.GetAwaiter().GetResult();
}
finally
{
if (restoreContext) SynchronizationContext.SetSynchronizationContext(originalContext);
}
}
}
並按如下方式使用它
public void MySyncFunction()
{
try
{
AsyncTask.Run(() => MyAsyncDriverFunction());
}
catch (Exception exp)
{
}
}
會在沒有僵局的情況下做你所要求的。 關鍵是在異步任務執行期間“隱藏”當前同步上下文,並強制使用默認同步上下文,該上下文已知將線程池用於Post
方法。 同樣,我不確定這是好主意還是壞主意,也不確定它會帶來什么副作用,但是一旦你問了,我只是分享它。
嘗試將“await AsyncExternalLibraryFunction1()”更改為“AsyncExternalLibraryFunction1().Wait()”並在其旁邊,並刪除函數“MyAsyncDriverFunction”的異步
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.