วิธีการเรียก async แบบซิงโครนัส


231

ฉันมีasyncวิธี:

public async Task<string> GenerateCodeAsync()
{
    string code = await GenerateCodeService.GenerateCodeAsync();
    return code;
}

ฉันต้องเรียกวิธีนี้จากวิธีการแบบซิงโครนัส

ฉันจะทำสิ่งนี้ได้โดยไม่ต้องทำซ้ำGenerateCodeAsyncวิธีเพื่อให้สามารถทำงานพร้อมกันได้หรือไม่

ปรับปรุง

ยังไม่พบวิธีแก้ไขที่สมเหตุสมผล

อย่างไรก็ตามฉันเห็นว่าHttpClientใช้รูปแบบนี้แล้ว

using (HttpClient client = new HttpClient())
{
    // async
    HttpResponseMessage responseAsync = await client.GetAsync(url);

    // sync
    HttpResponseMessage responseSync = client.GetAsync(url).Result;
}


1
ฉันหวังว่าจะมีวิธีแก้ปัญหาที่ง่ายกว่าโดยคิดว่า asp.net จัดการสิ่งนี้ได้ง่ายกว่าการเขียนโค้ดหลายบรรทัด
Catalin

ทำไมไม่เพียงแค่ยอมรับรหัส async? เป็นการดีที่คุณต้องการรหัส async เพิ่มเติมไม่น้อย
เปาโล Morgado

54
[ทำไมไม่เพียงแค่กอดรหัส async?] อาจเป็นเพราะเรากำลังรวบรวมรหัส async ที่ต้องการโซลูชันนี้เนื่องจากส่วนใหญ่ของโครงการได้รับการแปลง! คุณไม่สามารถสร้างกรุงโรมได้ในหนึ่งวัน
Nicholas Petersen

1
@NicholasPetersen บางครั้งห้องสมุดบุคคลที่สามสามารถบังคับให้คุณทำเช่นนี้ ตัวอย่างการสร้างข้อความแบบไดนามิกในวิธี WithMessage จาก FluentValidation ไม่มี async API สำหรับสิ่งนี้เนื่องจากการออกแบบไลบรารี - WithMessage overloads เป็นแบบคงที่ วิธีการอื่นในการส่งอาร์กิวเมนต์แบบไดนามิกไปยัง WithMessage นั้นแปลก
H.Wojtowicz

คำตอบ:


278

คุณสามารถเข้าถึงResultคุณสมบัติของภารกิจซึ่งจะทำให้เธรดของคุณบล็อกจนกว่าผลลัพธ์จะพร้อมใช้งาน:

string code = GenerateCodeAsync().Result;

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

นี่ไม่ได้หมายความว่าคุณควรเพิ่มแอสซิงก์.ConfigureAwait(false)ทุกครั้งที่โทรเข้า! สำหรับการวิเคราะห์โดยละเอียดเกี่ยวกับสาเหตุและเวลาที่คุณควรใช้.ConfigureAwait(false)ดูโพสต์บล็อกต่อไปนี้:


33
หากการเรียกใช้มีresultความเสี่ยงการหยุดชะงักดังนั้นเมื่อใดจึงปลอดภัยที่จะได้รับผลลัพธ์ ทุกการโทรแบบอะซิงโครนัสต้องการTask.RunหรือConfigureAwait(false)ไม่?
Robert Harvey

4
ไม่มี "หัวข้อหลัก" ใน ASP.NET (ซึ่งแตกต่างจากแอพพลิเค GUI) แต่การหยุดชะงักยังคงเป็นไปเพราะวิธี AspNetSynchronizationContext.Post serializes async ต:Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
noseratio

4
@RobertHarvey: หากคุณไม่สามารถควบคุมการใช้วิธีการ async ที่คุณบล็อกได้คุณต้องปิดมันด้วยTask.Runเพื่อความปลอดภัย หรือใช้สิ่งที่ต้องการWithNoContextลดการสลับเธรดที่ซ้ำซ้อน
noseratio

10
หมายเหตุ: การโทร.Resultยังคงหยุดชะงักหากผู้เรียกอยู่บนเธรดพูล ใช้สถานการณ์สมมติที่กลุ่มเธรดมีขนาด 32 และ 32 กำลังทำงานอยู่และWait()/Resultรองานที่ 33 ที่ยังไม่ได้จัดกำหนดการที่ต้องการเรียกใช้งานหนึ่งในเธรดที่รออยู่
Warty

55

คุณควรจะได้รับ waititer ( GetAwaiter()) และจบการรอเพื่อให้งาน asynchronous ( GetResult()) เสร็จสมบูรณ์

string code = GenerateCodeAsync().GetAwaiter().GetResult();

38
เราพบปัญหาการหยุดชะงักโดยใช้วิธีนี้ ถูกเตือน
โอลิเวอร์

6
MSDNTask.GetAwaiter : วิธีนี้มีไว้สำหรับคอมไพเลอร์ใช้แทนการใช้ในรหัสแอปพลิเคชัน
foka

ฉันยังคงมีข้อผิดพลาดป๊อปอัพโต้ตอบ (กับความประสงค์ของฉัน) กับปุ่ม 'สลับไปยัง' หรือ 'ลองใหม่' …. อย่างไรก็ตามการเรียกใช้งานจริงและกลับมาพร้อมกับการตอบสนองที่เหมาะสม
Jonathan Hansen

30

คุณควรทำสิ่งนี้ให้สำเร็จโดยใช้ตัวแทนแลมบ์ดา

private void button2_Click(object sender, EventArgs e)
    {

        label1.Text = "waiting....";

        Task<string> sCode = Task.Run(async () =>
        {
            string msg =await GenerateCodeAsync();
            return msg;
        });

        label1.Text += sCode.Result;

    }

    private Task<string> GenerateCodeAsync()
    {
        return Task.Run<string>(() => GenerateCode());
    }

    private string GenerateCode()
    {
        Thread.Sleep(2000);
        return "I m back" ;
    }

ตัวอย่างนี้จะไม่รวบรวม ชนิดส่งคืนจาก Task.Run คือ Task ดูบล็อก MSDNนี้สำหรับคำอธิบายแบบเต็ม
Appetere

5
ขอบคุณที่ชี้ให้เห็นใช่มันจะส่งคืนประเภทงาน การแทนที่ "string sCode" เป็น Task <string> หรือ var sCode ควรแก้ไข การเพิ่มโค้ดคอมไพล์แบบเต็มเพื่อความสะดวก
Faiyaz

20

ฉันต้องเรียกวิธีนี้จากวิธีการแบบซิงโครนัส

เป็นไปได้ด้วยGenerateCodeAsync().ResultหรือGenerateCodeAsync().Wait()ตามคำตอบอื่น ๆ ที่แนะนำ สิ่งนี้จะบล็อกเธรดปัจจุบันจนกว่าจะGenerateCodeAsyncเสร็จสมบูรณ์

อย่างไรก็ตามคำถามของคุณจะถูกติดแท็กด้วย และคุณแสดงความคิดเห็นด้วย:

ฉันหวังว่าจะเป็นวิธีที่ง่ายกว่าโดยคิดว่า asp.net จัดการสิ่งนี้ได้ง่ายกว่าการเขียนโค้ดหลายบรรทัด

ประเด็นของฉันคือคุณไม่ควรบล็อกวิธีอะซิงโครนัสใน ASP.NET นี้จะช่วยลดความยืดหยุ่นของ app เว็บของคุณและอาจสร้างการหยุดชะงัก (เมื่อawaitอยู่ภายในต่อเนื่องGenerateCodeAsyncมีการโพสต์AspNetSynchronizationContext) การใช้Task.Run(...).Resultเพื่อถ่ายบางสิ่งบางอย่างไปยังเธรดพูลและบล็อกจะทำให้ความสามารถในการขยายเพิ่มมากขึ้นเนื่องจากมีเธรด +1 เพิ่มขึ้นเพื่อประมวลผลคำขอ HTTP ที่กำหนด

ASP.NET มีการสนับสนุนวิธีการแบบอะซิงโครนัสในตัวผ่านการควบคุมแบบอะซิงโครนัส (ใน ASP.NET MVC และ Web API) หรือผ่านทางโดยตรงAsyncManagerและPageAsyncTaskใน ASP.NET แบบคลาสสิก คุณควรใช้มัน สำหรับรายละเอียดเพิ่มเติมให้ตรวจสอบคำตอบนี้


ฉันเขียนทับSaveChanges()วิธีของDbContextและที่นี่ฉันกำลังเรียกวิธีการ async ดังนั้นตัวควบคุม async น่าเสียดายที่จะไม่ช่วยฉันในสถานการณ์นี้
Catalin

3
@RaraituL โดยทั่วไปคุณไม่ต้องผสม async และรหัสซิงค์เลือกรุ่น euther คุณสามารถใช้ทั้งสองSaveChangesAsyncและSaveChangesเพียงให้แน่ใจว่าพวกเขาไม่ได้รับการเรียกทั้งในโครงการ ASP.NET เดียวกัน
noseratio

4
.NET MVCตัวกรองบางตัวไม่รองรับโค้ดแบบอะซิงโครนัสIAuthorizationFilterดังนั้นฉันจึงไม่สามารถใช้งานได้asyncตลอด
Catalin

3
@Noseratio ที่เป็นเป้าหมายที่ไม่สมจริง มีไลบรารีจำนวนมากเกินไปที่มีโค้ดอะซิงโครนัสและซิงโครนัสรวมถึงสถานการณ์ที่ไม่สามารถใช้โมเดลเดียวได้ MVC ActionFilters ไม่สนับสนุนโค้ดแบบอะซิงโครนัส
Justin Skiles

9
@Noserato คำถามเกี่ยวกับการเรียกใช้วิธีการแบบอะซิงโครนัสจากแบบซิงโครนัส บางครั้งคุณไม่สามารถเปลี่ยน API ที่คุณใช้งานได้ สมมติว่าคุณกำลังใช้อินเทอร์เฟซแบบซิงโครนัสจากเฟรมเวิร์กปาร์ตี้ 3-rd "A" (คุณไม่สามารถเขียนเฟรมเป็นแบบอะซิงโครนัสได้) แต่ไลบรารีบุคคลที่สาม "B" ที่คุณพยายามใช้ในการนำไปใช้ นอกจากนี้ผลิตภัณฑ์ที่ได้ยังเป็นไลบรารีและสามารถใช้งานได้ทุกที่รวมถึง ASP.NET และอื่น ๆ อีกมากมาย
dimzon

19

Microsoft Identity มีวิธีส่วนขยายที่เรียกใช้วิธีการแบบซิงโครนัสพร้อมกัน ตัวอย่างเช่นมีวิธี GenerateUserIdentityAsync () และ CreateIdentity เท่ากัน ()

หากคุณดู UserManagerExtensions.CreateIdentity () มันจะมีลักษณะดังนี้:

 public static ClaimsIdentity CreateIdentity<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user,
        string authenticationType)
        where TKey : IEquatable<TKey>
        where TUser : class, IUser<TKey>
    {
        if (manager == null)
        {
            throw new ArgumentNullException("manager");
        }
        return AsyncHelper.RunSync(() => manager.CreateIdentityAsync(user, authenticationType));
    }

ตอนนี้มาดูสิ่งที่ AsyncHelper.RunSync ทำ

  public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        var cultureUi = CultureInfo.CurrentUICulture;
        var culture = CultureInfo.CurrentCulture;
        return _myTaskFactory.StartNew(() =>
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = cultureUi;
            return func();
        }).Unwrap().GetAwaiter().GetResult();
    }

ดังนั้นนี่คือ wrapper ของคุณสำหรับวิธี async และโปรดอย่าอ่านข้อมูลจากผลลัพธ์ - มันอาจบล็อกรหัสของคุณใน ASP

มีวิธีอื่น - ซึ่งเป็นที่น่าสงสัยสำหรับฉัน แต่คุณสามารถพิจารณาด้วย

  Result r = null;

            YourAsyncMethod()
                .ContinueWith(t =>
                {
                    r = t.Result;
                })
                .Wait();

3
คุณคิดว่าปัญหานี้เกิดจากวิธีที่สองที่คุณแนะนำ
David Clarke

@DavidClarke อาจเป็นปัญหาด้านความปลอดภัยของเธรดในการเข้าถึงตัวแปรที่ไม่ลบเลือนจากหลายเธรดโดยไม่มีการล็อก
Theodor Zoulias

9

เพื่อป้องกันการหยุดชะงักฉันพยายามใช้ทุกTask.Run()ครั้งเมื่อฉันต้องเรียกใช้วิธีการแบบซิงโครนัสพร้อมกันที่ @Hinzi กล่าวถึง

อย่างไรก็ตามวิธีนี้จะต้องมีการแก้ไขหากวิธีการซิงค์ใช้พารามิเตอร์ ตัวอย่างเช่นTask.Run(GenerateCodeAsync("test")).Resultให้ข้อผิดพลาด:

อาร์กิวเมนต์ 1: ไม่สามารถแปลงจาก ' System.Threading.Tasks.Task<string>' เป็น 'System.Action'

อาจเรียกสิ่งนี้ได้เช่นนี้แทน:

string code = Task.Run(() => GenerateCodeAsync("test")).Result;

6

คำตอบส่วนใหญ่ในกระทู้นี้มีความซับซ้อนหรืออาจทำให้เกิดการหยุดชะงัก

วิธีการต่อไปนี้เป็นวิธีที่ง่ายและจะหลีกเลี่ยงการหยุดชะงักเนื่องจากเรากำลังรอให้ภารกิจเสร็จสิ้นและหลังจากนั้นจึงได้รับผลลัพธ์ -

var task = Task.Run(() => GenerateCodeAsync()); 
task.Wait();
string code = task.Result;

นอกจากนี้นี่คือการอ้างอิงถึงบทความ MSDN ที่พูดถึงสิ่งเดียวกัน - https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result-สิ่งเดียวกัน ในหลักบริบท /


1

ฉันชอบวิธีที่ไม่มีการบล็อก:

            Dim aw1=GenerateCodeAsync().GetAwaiter()
            While Not aw1.IsCompleted
                Application.DoEvents()
            End While

0

ฉันใช้วิธีนี้:

    private string RunSync()
    {
        var task = Task.Run(async () => await GenerateCodeService.GenerateCodeAsync());
        if (task.IsFaulted && task.Exception != null)
        {
            throw task.Exception;
        }

        return task.Result;
    }

-1

วิธีอื่นอาจเป็นถ้าคุณต้องการรอจนกว่างานจะเสร็จสิ้น:

var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;

1
มันผิดธรรมดา WhenAll ยังส่งคืนภารกิจซึ่งคุณไม่ได้รอ
Robert Schmidt

-1

แก้ไข:

งานมีวิธีการรอ, Task.Wait () ซึ่งรอให้ "สัญญา" เพื่อแก้ไขและดำเนินการต่อดังนั้นจึงแสดงผลแบบซิงโครนัส ตัวอย่าง:


async Task<String> MyAsyncMethod() { ... }

String mySyncMethod() {

    return MyAsyncMethod().Wait();
}

3
กรุณาอธิบายอย่างละเอียดในคำตอบของคุณ มันใช้งานอย่างไร? การตอบคำถามเป็นอย่างไร
Scratte

-2

หากคุณมีเมธอด async ชื่อ " RefreshList " คุณสามารถเรียกเมธอด async นั้นได้จากวิธีที่ไม่ใช่ async เช่นด้านล่าง

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