วิธีการเรียกใช้วิธีการแบบอะซิงโครนัสจากวิธีแบบซิงโครนัสใน C # ได้อย่างไร


862

ฉันมีpublic async void Foo()วิธีที่ฉันต้องการโทรจากวิธีการซิงโครนัส จนถึงตอนนี้ที่ฉันได้เห็นจากเอกสาร MSDN กำลังเรียกวิธี async ผ่านวิธี async แต่โปรแกรมทั้งหมดของฉันไม่ได้ถูกสร้างขึ้นด้วยวิธี async

เป็นไปได้ไหม

นี่คือตัวอย่างหนึ่งของการเรียกวิธีการเหล่านี้จากวิธีอะซิงโครนัส: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

ตอนนี้ฉันกำลังมองหาวิธีการซิงค์จากวิธีการซิงค์


2
ฉันวิ่งเข้าไปในนี้เช่นกัน การแทนที่ RoleProvider คุณไม่สามารถเปลี่ยนวิธีการลงลายมือชื่อของวิธี GetRolesForUser ดังนั้นคุณจึงไม่สามารถสร้างวิธีการแบบ async ได้ดังนั้นจึงไม่สามารถใช้การรอคอยที่จะโทรออกไปยัง api แบบอะซิงโครนัสได้ โซลูชันชั่วคราวของฉันคือการเพิ่มวิธีการซิงโครนัสให้กับคลาส HttpClient ทั่วไปของฉัน แต่ต้องการทราบว่าเป็นไปได้หรือไม่ (และความหมายที่อาจเกิดขึ้น)
ทิโมธีลีรัสเซล

1
เนื่องจากasync void Foo()วิธีการของคุณไม่ส่งคืนTaskหมายความว่าผู้โทรไม่สามารถทราบได้เมื่อดำเนินการเสร็จสิ้นจึงต้องส่งคืนTaskแทน
Dai

1
การลิงก์q / a ที่เกี่ยวข้องเกี่ยวกับวิธีการทำเช่นนี้ในเธรด UI
noseratio

คำตอบ:


711

การเขียนโปรแกรมแบบอะซิงโครนัส "เติบโต" ผ่านฐานรหัส จะได้รับเมื่อเทียบกับไวรัสผีดิบ ทางออกที่ดีที่สุดคือให้มันเติบโต แต่บางครั้งก็เป็นไปไม่ได้

ฉันได้เขียนบางชนิดในไลบรารีNito.AsyncExของฉันเพื่อจัดการกับฐานรหัสแบบอะซิงโครนัสบางส่วน ไม่มีวิธีแก้ปัญหาที่สามารถใช้ได้ในทุกสถานการณ์

โซลูชัน A

หากคุณมีวิธีการแบบอะซิงโครนัสอย่างง่ายที่ไม่จำเป็นต้องซิงโครไนซ์กลับไปที่บริบทของมันคุณสามารถใช้Task.WaitAndUnwrapException:

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

คุณไม่ต้องการใช้Task.WaitหรือTask.Resultเพราะมันห่อข้อยกเว้นAggregateExceptionไว้

โซลูชันนี้เหมาะสมถ้าMyAsyncMethodไม่ซิงโครไนซ์กลับไปที่บริบท ในคำอื่น ๆ ทุกawaitในควรจะจบลงด้วยการMyAsyncMethod ConfigureAwait(false)หมายความว่าไม่สามารถอัปเดตองค์ประกอบ UI หรือเข้าถึงบริบทคำขอ ASP.NET

โซลูชัน B

หากMyAsyncMethodไม่จำเป็นต้องซิงโครไนซ์กลับไปที่บริบทของมันคุณอาจจะสามารถใช้AsyncContext.RunTaskเพื่อให้บริบทที่ซ้อนกัน:

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

* อัปเดต 4/14/2014: ในไลบรารีเวอร์ชันล่าสุด API จะเป็นดังนี้:

var result = AsyncContext.Run(MyAsyncMethod);

(มันก็โอเคที่จะใช้Task.Resultในตัวอย่างนี้เพราะRunTaskจะเผยแพร่Taskข้อยกเว้น)

เหตุผลที่คุณอาจต้องการAsyncContext.RunTaskแทนที่จะTask.WaitAndUnwrapExceptionเป็นเพราะความเป็นไปได้ของการหยุดชะงักที่ค่อนข้างละเอียดอ่อนที่เกิดขึ้นใน WinForms / WPF / SL / ASP.NET:

  1. วิธีการแบบซิงโครนัสเรียกวิธีการแบบอะซิงโครนัTask
  2. Taskวิธีการซิงโครไม่รอการปิดกั้นบน
  3. asyncวิธีการใช้งานโดยไม่ต้องawaitConfigureAwait
  4. Taskไม่สามารถดำเนินการในสถานการณ์เช่นนี้เพราะเพียงเสร็จสมบูรณ์เมื่อasyncวิธีการเสร็จสิ้น; asyncวิธีการไม่สามารถดำเนินการเพราะมันเป็นความพยายามที่จะกำหนดเวลาต่อเนื่องไปยังSynchronizationContextและ WinForms / WPF / SL / ASP.NET จะไม่ยอมให้ความต่อเนื่องในการทำงานเพราะวิธีการซิงโครทำงานอยู่แล้วในบริบทที่

นี่คือเหตุผลหนึ่งว่าทำไมจึงควรใช้ConfigureAwait(false)ภายในทุกasyncวิธีให้มากที่สุด

โซลูชัน C

AsyncContext.RunTaskจะไม่ทำงานในทุกสถานการณ์ ตัวอย่างเช่นหากasyncวิธีการรอสิ่งที่ต้องการให้เหตุการณ์ UI เสร็จสมบูรณ์คุณจะหยุดชะงักแม้จะอยู่ในบริบทที่ซ้อนกัน ในกรณีนั้นคุณสามารถเริ่มasyncวิธีการในกลุ่มเธรด:

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

อย่างไรก็ตามโซลูชันนี้ต้องการ a MyAsyncMethodที่จะทำงานในบริบทเธรดพูล ดังนั้นจึงไม่สามารถอัปเดตองค์ประกอบ UI หรือเข้าถึงบริบทคำขอ ASP.NET และในกรณีที่คุณได้เป็นอย่างดีอาจจะเพิ่มConfigureAwait(false)การของawaitงบและใช้วิธีการแก้ปัญหาเอ

ปรับปรุง 2019/05/01:ปัจจุบัน "น้อยที่เลวร้ายที่สุดการปฏิบัติ" อยู่ในบทความ MSDN ที่นี่


9
โซลูชัน A ดูเหมือนว่าสิ่งที่ฉันต้องการ แต่ดูเหมือน task.WaitAndUnwrapException () ไม่ได้ทำให้เป็น. NET 4.5 RC มันมีเพียง task.Wait () มีความคิดว่าจะทำอย่างไรกับเวอร์ชันใหม่นี้? หรือนี่เป็นวิธีส่วนขยายที่คุณกำหนดเองหรือไม่
deadlydog

3
WaitAndUnwrapExceptionเป็นวิธีการของตัวเองของฉันจากห้องสมุด AsyncEx lib. NET อย่างเป็นทางการไม่ได้ให้ความช่วยเหลือในการผสมการซิงค์และรหัส async (และโดยทั่วไปคุณไม่ควรทำ!) ฉันกำลังรอ. NET 4.5 RTW และแล็ปท็อปที่ไม่ใช่ XP ใหม่ก่อนที่จะอัปเดต AsyncEx ให้ทำงานบน 4.5 (ปัจจุบันฉันไม่สามารถพัฒนา 4.5 ได้เพราะฉันติดอยู่กับ XP นานสองสามสัปดาห์)
Stephen Cleary

12
AsyncContextตอนนี้มีRunวิธีการที่แสดงออกแลมบ์ดาดังนั้นคุณควรใช้var result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary

1
ฉันเอาไลบรารี่ของคุณออกจาก Nuget แล้ว แต่ดูเหมือนว่าจะไม่มีRunTaskวิธี สิ่งที่ใกล้เคียงที่สุดที่ฉันจะหาได้Runแต่นั่นไม่มีResultทรัพย์สิน
ซาด Saeeduddin

3
@ Asad: ใช่มากกว่า 2 ปีต่อมา API มีการเปลี่ยนแปลง ตอนนี้คุณสามารถพูดได้var result = AsyncContext.Run(MyAsyncMethod);
Stephen Cleary

313

การเพิ่มวิธีแก้ไขปัญหาของฉันในที่สุดก็หวังว่าจะช่วยประหยัดเวลาของใครบางคน

ก่อนอื่นอ่านบทความคู่ของStephen Cleary :

จาก "วิธีปฏิบัติที่ดีที่สุดสองข้อ" ใน "ไม่ปิดกั้นรหัส Async" วิธีแรกไม่ได้ผลสำหรับฉันและวิธีที่สองไม่สามารถใช้ได้ (โดยทั่วไปถ้าฉันสามารถใช้ได้awaitฉันทำได้!)

ดังนั้นนี่คือวิธีแก้ปัญหาของฉัน: ปิดการโทรภายในTask.Run<>(async () => await FunctionAsync());และหวังว่าจะไม่มีการหยุดชะงักอีกต่อไป

นี่คือรหัสของฉัน:

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;
    }
}

5
สองปีที่ผ่านมาฉันอยากรู้ว่าโซลูชันนี้ใช้งานได้อย่างไร ข่าวใด ๆ? มีความละเอียดอ่อนต่อวิธีการนี้ที่หายไปกับมือใหม่หรือไม่?
Dan Esparza

26
สิ่งนี้จะไม่หยุดชะงักจริง แต่เพียงเพราะมันถูกบังคับให้ทำงานในเธรดใหม่นอกบริบทการซิงโครไนซ์ของเธรดที่มา อย่างไรก็ตามมีสภาพแวดล้อมบางอย่างที่สิ่งนี้ไม่สมควร: โดยเฉพาะอย่างยิ่งแอปพลิเคชันบนเว็บ สิ่งนี้สามารถลดเธรดที่พร้อมใช้งานได้ครึ่งหนึ่งสำหรับเว็บเซิร์ฟเวอร์ (หนึ่งเธรดสำหรับคำร้องขอและอีกหนึ่งเธรดสำหรับสิ่งนี้) ยิ่งคุณทำสิ่งนี้มากเท่าไหร่ก็ยิ่งได้รับมากขึ้นเท่านั้น คุณอาจสิ้นสุด deadlocking เว็บเซิร์ฟเวอร์ทั้งหมดของคุณ
Chris Pratt

30
@ChrisPratt - คุณอาจพูดถูกเพราะTask.Run()ไม่ใช่วิธีปฏิบัติที่ดีที่สุดในรหัส async แต่อีกครั้งคำตอบสำหรับคำถามเดิมคืออะไร ไม่เคยเรียกวิธีการ async แบบซิงโครนัสหรือไม่ เราต้องการ แต่ในโลกแห่งความเป็นจริงบางครั้งเราต้องทำ
Tohid

1
@Tohid คุณสามารถลองใช้ห้องสมุดของ Stephen Cleary ฉันเคยเห็นผู้คนสันนิษฐานว่าสิ่งนี้และParallel.ForEachการใช้ในทางที่ผิดจะไม่มีผลใน 'โลกแห่งความจริง' และในที่สุดมันก็ลงเซิร์ฟเวอร์ รหัสนี้ใช้ได้สำหรับแอป Console แต่อย่างที่ @ChrisPratt บอกว่าไม่ควรใช้ใน Web Apps อาจใช้งานได้ "ตอนนี้" แต่ไม่สามารถปรับขนาดได้
makhdumi

1
ฉันรู้สึกสนใจที่จะเริ่มสร้างบัญชีใหม่ในการตอบคำถามเพียงเพื่อให้ได้คะแนนมากพอที่จะอัปเกรดบัญชีนี้ ....
Giannis Paraskevopoulos

206

Microsoft สร้างคลาส AsyncHelper (ภายใน) เพื่อเรียกใช้ Async เป็น Sync แหล่งที่มามีลักษณะดังนี้:

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();
    }
}

คลาสพื้นฐาน Microsoft.AspNet.Identity มีเมธอด Async เท่านั้นและเพื่อเรียกใช้เป็น Sync มีคลาสที่มีเมธอดส่วนขยายที่มีลักษณะดังนี้ (ตัวอย่างการใช้งาน):

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));
}

สำหรับผู้ที่กังวลเกี่ยวกับเงื่อนไขการอนุญาตให้ใช้สิทธิ์ของรหัสนี่คือลิงค์ไปยังรหัสที่คล้ายกันมาก (เพียงเพิ่มการสนับสนุนวัฒนธรรมในเธรด) ที่มีความคิดเห็นเพื่อระบุว่าเป็น MIT ได้รับอนุญาตจาก Microsoft https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs


2
เมธอด async ของฉันรอเมธอด async อื่น ๆ ผมไม่ตกแต่งใด ๆ ของฉันโทรด้วยawait ConfigureAwait(false)ฉันพยายามใช้AsyncHelper.RunSyncเพื่อเรียกใช้ฟังก์ชัน async จากApplication_Start()ฟังก์ชันใน Global.asax และดูเหมือนว่าจะใช้งานได้ นี่หมายถึงว่าAsyncHelper.RunSyncมีแนวโน้มที่จะไม่เกิดปัญหาการหยุดชะงัก "marshal กลับสู่บริบทของผู้โทร" ที่ฉันอ่านเกี่ยวกับที่อื่นในการโพสต์นี้หรือไม่?
Bob.at.Indigo.Health

1
@ Bob.at.SBS ขึ้นอยู่กับว่าคุณใช้รหัสอะไร มันไม่ง่ายอย่างที่หากฉันใช้รหัสนี้นผมปลอดภัย นี่เป็นวิธีที่น้อยที่สุดและกึ่งปลอดภัยในการเรียกใช้คำสั่ง async แบบซิงโครนัสสามารถใช้งานได้อย่างไม่เหมาะสมเพื่อทำให้เกิดการหยุดชะงัก
Erik Philips

1
ขอบคุณ 2 คำถามติดตามผล: 1) คุณสามารถยกตัวอย่างของวิธีการ async ที่ต้องการหลีกเลี่ยงที่จะทำให้เกิดการหยุดชะงักและ 2) การหยุดชะงักในบริบทนี้มักขึ้นอยู่กับเวลา หากใช้งานได้จริงฉันจะยังคงมีการหยุดชะงักตามเวลาที่ซ่อนอยู่ในรหัสของฉันหรือไม่
Bob.at.Indigo.Health

@ Bob.at.SBS ฉันอยากแนะนำให้ถามคำถามโดยใช้ปุ่มถามคำถามที่มุมบนขวา คุณสามารถรวมลิงค์ไปยังคำถามหรือคำตอบในคำถามของคุณเพื่อเป็นข้อมูลอ้างอิง
Erik Philips

1
@ Bob.at ... รหัสที่ให้โดย Erik นั้นสมบูรณ์แบบภายใต้ Asp net mvc5 และ EF6 แต่ไม่ใช่เมื่อฉันลองวิธีแก้ไขปัญหาอื่น ๆ (ConfigureAwait (false) .GetAwaiter (). GetResult () หรือ. result) ซึ่งแฮงค์แอปพลิเคชันเว็บของฉันโดยสมบูรณ์
LeonardoX

150

async Main ตอนนี้เป็นส่วนหนึ่งของ C # 7.2 และสามารถเปิดใช้งานในการตั้งค่าการสร้างขั้นสูงของโครงการ

สำหรับ C # <7.2 วิธีที่ถูกต้องคือ:

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


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

คุณจะเห็นสิ่งนี้ใช้ในเอกสารของ Microsoft จำนวนมากตัวอย่างเช่น: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- หัวข้อการสมัครสมาชิก


11
ฉันไม่รู้ว่าทำไมมีคนโหวตเรื่องนี้ มันใช้งานได้ดีสำหรับฉัน หากไม่มีการแก้ไขนี้ฉันจะต้องเผยแพร่ ASYCH ทุกที่
ศูนย์นักโทษ

11
นี่คือเหตุผลที่ดีกว่าMainAsync().Wait()?
บดขยี้

8
ฉันเห็นด้วย. คุณเพียงแค่ต้องการ MainAsync () รอ () แทนทั้งหมดนี้
ฮัจจัต

8
@ crush ฉันอธิบายว่าสิ่งนี้สามารถหลีกเลี่ยงการหยุดชะงักบางอย่าง ในบางสถานการณ์การเรียก. Wait () จาก UI หรือ asp.net thread ทำให้เกิดการหยุดชะงัก async deadlocks
David

6
@ClintB: คุณไม่ควรทำอย่างนี้ใน ASP.NET Core แอปพลิเคชันบนเว็บมีความเสี่ยงเป็นพิเศษที่จะติดดาวด้ายและทุกครั้งที่คุณทำเช่นนี้คุณจะดึงเธรดจากพูลที่อาจใช้เพื่อรับคำขอ มันมีปัญหาน้อยกว่าสำหรับเดสก์ท็อป / แอปพลิเคชันมือถือเนื่องจากเป็นแบบผู้ใช้เดี่ยว
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

}

คุณอ่านคำสำคัญ 'รอ' เป็น "เริ่มงานที่ยาวนานนี้แล้วกลับควบคุมวิธีการโทร" เมื่องานที่ดำเนินการมายาวนานนั้นเสร็จสิ้นแล้วมันจะเรียกใช้งานโค้ดหลังจากนั้น รหัสหลังจากการรอคอยจะคล้ายกับที่เคยเป็นเมธอด CallBack ความแตกต่างที่สำคัญของการไหลแบบลอจิคัลคือไม่ถูกขัดจังหวะซึ่งทำให้ง่ายต่อการเขียนและอ่าน


15
Waitล้อมข้อยกเว้นและมีความเป็นไปได้ของการหยุดชะงัก
Stephen Cleary

ฉันคิดว่าถ้าคุณเรียกวิธีการ async โดยไม่ใช้awaitมันจะถูกดำเนินการพร้อมกัน อย่างน้อยก็ใช้ได้กับฉัน (โดยไม่ต้องโทรmyTask.Wait) ที่จริงฉันได้รับข้อยกเว้นเมื่อฉันพยายามโทรmyTask.RunSynchronously()เพราะมันถูกดำเนินการแล้ว!
ความกลัว

2
ฉันชอบคำตอบนี้ ความคิดเห็นที่ดีสำหรับการแก้ไขเล็กและสง่างาม ขอบคุณสำหรับการสนับสนุน! ฉันยังคงเรียนรู้การทำงานร่วมกันดังนั้นทุกอย่างช่วย :)
kayleeFrye_onDeck

2
คำตอบนี้ควรจะยังคงเป็นของวันนี้? ฉันแค่พยายามมันในโครงการ MVC มีดโกนและ app .Resultเพียงแฮงค์ในการเข้าถึง
Gone Coding

7
@TrueBlueAussie นั่นคือการหยุดชะงักของบริบทการซิงค์ รหัส async ของคุณจะกลับไปที่บริบทการซิงโครไนซ์ แต่จะถูกบล็อกโดยการResultโทรในเวลานั้นดังนั้นจึงไม่เคยไปถึงที่นั่น และResultไม่มีที่สิ้นสุดเพราะกำลังรอใครซักคนที่กำลังรอให้Resultถึงจุดจบโดยทั่วไป: D
Luaan

40

ฉันไม่แน่ใจ 100% แต่ฉันเชื่อว่าเทคนิคที่อธิบายไว้ในบล็อกนี้ควรใช้งานได้ในหลาย ๆ สถานการณ์:

คุณสามารถใช้task.GetAwaiter().GetResult()หากคุณต้องการเรียกใช้ตรรกะการเผยแพร่นี้โดยตรง


6
โซลูชัน A ในคำตอบของ Stephen Cleary ด้านบนใช้วิธีนี้ ดูแหล่งWaitAndUnwrapException
orad

คุณจำเป็นต้องใช้ GetResult () ถ้าฟังก์ชั่นที่คุณกำลังเรียกใช้นั้นเป็นโมฆะหรือไม่? ฉันหมายความว่าถ้าคุณไม่ต้องการให้ผลลัพธ์ใด ๆ กลับคืนมา
batmaci

ใช่มิฉะนั้นจะไม่ปิดกั้นจนกว่างานจะเสร็จ อีกทางเลือกหนึ่งแทนการโทร GetAwaiter (). GetResult () คุณสามารถโทร. Wait ()
NStuke

1
นั่นคือส่วน "สถานการณ์มากมาย" ขึ้นอยู่กับโมเดลการทำเกลียวโดยรวมและสิ่งที่เธรดอื่นกำลังทำเพื่อพิจารณาว่ามีความเสี่ยงที่จะเกิดการหยุดชะงักหรือไม่
NStuke

24

อย่างไรก็ตามมีทางออกที่ดีที่ทำงานใน (เกือบ: ดูความคิดเห็น) ทุกสถานการณ์: ปั๊มข้อความแบบเฉพาะกิจ (SynchronizationContext)

เธรดการโทรจะถูกบล็อกตามที่คาดไว้ในขณะที่ยังคงมั่นใจว่าการดำเนินการต่อเนื่องทั้งหมดที่เรียกจากฟังก์ชัน async จะไม่หยุดชะงักเนื่องจากจะถูก marshaled ไปยัง ad-hoc SynchronizationContext

รหัสของตัวช่วยปั๊มข้อความแบบเฉพาะกิจ:

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();
            }
        }
    }
}

การใช้งาน:

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

รายละเอียดเพิ่มเติมของปั๊ม async ที่มีอยู่ที่นี่


บริบทข้อยกเว้นและ AsyncPump stackoverflow.com/questions/23161693/...
PreguntonCojoneroCabrón

สิ่งนี้ไม่ทำงานในสถานการณ์ Asp.net เนื่องจากคุณอาจสูญเสีย HttpContext.Current แบบสุ่ม
Josh Mouch

12

ถึงทุกคนที่ให้ความสนใจกับคำถามนี้อีกต่อไป ...

ถ้าคุณดูในมีระดับที่เรียกว่าMicrosoft.VisualStudio.Services.WebApi TaskExtensionsภายในคลาสนั้นคุณจะเห็นวิธีการขยายแบบสแตติกTask.SyncResult()ซึ่งชอบทั้งหมดเพียงแค่บล็อกเธรดจนกว่างานจะส่งคืน

ภายในที่เรียกว่าtask.GetAwaiter().GetResult()ซึ่งเป็นเรื่องง่ายสวย แต่มันมากเกินไปในการทำงานใด ๆasyncวิธีการที่ผลตอบแทนTask, Task<T>หรือTask<HttpResponseMessage>... ประโยคน้ำตาลที่รัก ... ของพ่อมีฟันหวาน

ดูเหมือนว่า...GetAwaiter().GetResult()เป็นวิธีการอย่างเป็นทางการของ MS ในการรันโค้ด async ในบริบทการบล็อก ดูเหมือนว่าจะทำงานได้ดีมากสำหรับกรณีการใช้งานของฉัน


3
คุณมีฉันที่ "เหมือนแค่บล็อกทั้งหมด"
Dawood ibn Kareem

9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

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

หรือใช้สิ่งนี้:

var result=result.GetAwaiter().GetResult().AccessToken

6

คุณสามารถเรียกใช้วิธีการแบบอะซิงโครนัสใด ๆ จากรหัสซิงโครนัสซึ่งก็คือจนกว่าคุณจะต้องใช้วิธีawaitนี้ในกรณีนี้จะต้องทำเครื่องหมายasyncด้วยเช่นกัน

เนื่องจากมีผู้คนจำนวนมากกำลังแนะนำที่นี่คุณสามารถโทรหา Wait () หรือ Result กับงานที่เกิดขึ้นในวิธีการซิงโครนัสของคุณ แต่แล้วคุณก็จบลงด้วยการบล็อกการโทรในวิธีนั้นซึ่งเอาชนะวัตถุประสงค์ของ async

ฉันคุณไม่สามารถสร้างวิธีการของคุณasyncและคุณไม่ต้องการที่จะล็อควิธีการซิงโครนัสแล้วคุณจะต้องใช้วิธีการโทรกลับโดยส่งผ่านเป็นพารามิเตอร์ไปยังวิธีการ ContinueWith ในงาน


5
ถ้าอย่างนั้นจะไม่เรียกวิธีการนี้พร้อมกันไหม?
Jeff Mercado

2
ดังที่ฉันเข้าใจแล้วคำถามก็คือคุณสามารถเรียกวิธีการ async จากวิธีที่ไม่ใช่แบบอะซิงก์ สิ่งนี้ไม่ได้หมายความว่าจะต้องเรียกวิธีการ async ในลักษณะบล็อก
base2

ขออภัย "พวกเขาต้องทำเครื่องหมายasyncด้วย" ดึงความสนใจของฉันออกไปจากสิ่งที่คุณพูดจริงๆ
Jeff Mercado

ถ้าฉันไม่สนใจเกี่ยวกับความไม่เป็นอะซิงโครนัสมันก็โอเคที่จะเรียกมันว่าแบบนี้ (และสิ่งที่เกี่ยวกับความเป็นไปได้ของการล็อกในข้อยกเว้นที่สตีเฟ่นเคลียร์ยังคงจู้จี้อยู่) ที่ทดสอบวิธีการแบบอะซิงโครนัส ฉันต้องรอผลก่อนดำเนินการต่อเพื่อให้ฉันสามารถทดสอบผลลัพธ์ของวิธีการแบบอะซิงโครนัส
ความกลัว

6

ฉันรู้ว่าฉันมาช้า แต่ในกรณีที่มีคนอย่างฉันต้องการที่จะแก้ปัญหานี้ในวิธีที่ง่ายและไม่ต้องพึ่งพาห้องสมุดอื่น

ฉันพบโค้ดต่อไปนี้จาก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();
}

จากนั้นคุณสามารถเรียกมันว่าสิ่งนี้

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());

6
ดูเหมือนว่าคำตอบข้างต้นฉันจะทำบางสิ่งบางอย่าง
inlokesh

2

หลังจากชั่วโมงของการลองวิธีการต่าง ๆ ด้วยความสำเร็จที่มากหรือน้อยนี่คือสิ่งที่ฉันจบลงด้วย มันไม่ได้จบลงด้วยการหยุดชะงักในขณะที่ได้รับผลและมันยังได้รับและส่งข้อยกเว้นเดิมและไม่ใช่สิ่งที่ถูกห่อ

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

  return task.Result;
}

ทำงานร่วมกับ task returnGetAwaiter (). GetResult ();
ต่อ G

ใช่ แต่แล้วข้อยกเว้นเดิมล่ะ
JiříHerník

ฉันคิดว่าเหมือนกันโดยทั่วไปกับ. GetAwaiter () GetResult ()
ต่อ G

-2

มันอาจถูกเรียกมาจากเธรดใหม่ (ไม่ใช่จากเธรดพูล!):

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
  );

-3

วิธีการ async ของ windows นั้นมีวิธีการเล็ก ๆ ที่เรียกว่า AsTask () คุณสามารถใช้วิธีนี้เพื่อให้เมธอดส่งคืนตัวเองเป็นงานเพื่อให้คุณสามารถเรียก Wait () ด้วยตนเองได้

ตัวอย่างเช่นบนแอปพลิเคชัน Windows Phone 8 Silverlight คุณสามารถทำสิ่งต่อไปนี้:

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 
    // .....
}

หวังว่านี่จะช่วยได้!


-4

หากคุณต้องการเรียกใช้ซิงค์

MethodAsync().RunSynchronously()

3
วิธีนี้มีไว้สำหรับการเริ่มงานที่เย็น โดยทั่วไปวิธีการแบบอะซิงก์จะส่งคืนงานที่น่าสนใจหรืออาจกล่าวได้ว่าเป็นงานที่เริ่มต้นแล้ว โทรในผลงานร้อนไปยังRunSynchronously() InvalidOperationExceptionลองด้วยรหัสนี้: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;

2
สร้างการหยุดชะงัก ลบคำตอบที่ดีกว่า
PreguntonCojoneroCabrón

Task.Run (() => SaveAssetDataAsDraft ()) ผลลัพธ์; - ไม่สร้างการหยุดชะงัก
Anubis
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.