'รอ' ทำงาน แต่เรียกงานผลลัพธ์ค้าง / หยุดชะงัก


126

ฉันมีการทดสอบสี่ครั้งต่อไปนี้และการทดสอบครั้งสุดท้ายแฮงค์เมื่อฉันเรียกใช้ ทำไมสิ่งนี้จึงเกิดขึ้น:

[Test]
public void CheckOnceResultTest()
{
    Assert.IsTrue(CheckStatus().Result);
}

[Test]
public async void CheckOnceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceResultTest()
{
    Assert.IsTrue(CheckStatus().Result); // This hangs
    Assert.IsTrue(await CheckStatus());
}

private async Task<bool> CheckStatus()
{
    var restClient = new RestClient(@"https://api.test.nordnet.se/next/1");
    Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
    IRestResponse<DummyServiceStatus> response = await restResponse;
    return response.Data.SystemRunning;
}

ฉันใช้วิธีส่วนขยายนี้สำหรับRestsharp RestClient :

public static class RestClientExt
{
    public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
    {
        var tcs = new TaskCompletionSource<IRestResponse<T>>();
        RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
        return tcs.Task;
    }
}
public class DummyServiceStatus
{
    public string Message { get; set; }
    public bool ValidVersion { get; set; }
    public bool SystemRunning { get; set; }
    public bool SkipPhrase { get; set; }
    public long Timestamp { get; set; }
}

เหตุใดการทดสอบครั้งล่าสุดจึงค้าง


7
คุณควรหลีกเลี่ยงการคืนค่าเป็นโมฆะจากวิธี async ใช้สำหรับความเข้ากันได้แบบย้อนหลังกับตัวจัดการเหตุการณ์ที่มีอยู่ซึ่งส่วนใหญ่อยู่ในโค้ดอินเทอร์เฟซ หากวิธีการ async ของคุณไม่ส่งคืนอะไรเลยควรส่งคืน Task ฉันมีปัญหามากมายกับ MSTest และทำให้การทดสอบ async กลับมาเป็นโมฆะ
ghord

2
@ghord: MSTest ไม่สนับสนุนasync voidวิธีการทดสอบหน่วยเลย พวกเขาจะไม่ทำงาน อย่างไรก็ตาม NUnit ทำ ที่กล่าวว่าผมเห็นด้วยกับหลักการทั่วไปของการเลือกที่มากกว่าasync Task async void
Stephen Cleary

@StephenCleary ใช่แม้ว่าจะได้รับอนุญาตใน betas ของ VS2012 ซึ่งทำให้เกิดปัญหาทุกประเภท
ghord

คำตอบ:


88

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

ในกรณีนี้SynchronizationContextNUnit ใช้async voidวิธีทดสอบของคุณ ฉันจะลองใช้async Taskวิธีทดสอบแทน


4
การเปลี่ยนเป็น async Task ใช้งานได้ตอนนี้ฉันต้องอ่านเนื้อหาของลิงก์ของคุณสองสามครั้ง ty ครับ
Johan Larsson

@MarioLopez: วิธีแก้ปัญหาคือใช้ " asyncทุกทาง" (ตามที่ระบุไว้ในบทความ MSDN ของฉัน) กล่าวอีกนัยหนึ่ง - เป็นชื่อของบล็อกโพสต์ของฉัน - "don't block on async code"
Stephen Cleary

1
@StephenCleary ว่าถ้าฉันต้องเรียกเมธอด async ภายในคอนสตรัคเตอร์ล่ะ? ตัวสร้างไม่สามารถ async ได้
Raikol Amaro

1
@StephenCleary ในเกือบทุกการตอบของคุณใน SO และในบทความของคุณทั้งหมดที่ฉันไม่เคยเห็นคุณพูดคุยเกี่ยวกับการเปลี่ยนกับการทำวิธีการเรียกWait() asyncแต่สำหรับฉันแล้วสิ่งนี้ดูเหมือนจะผลักปัญหาไปที่ต้นน้ำ เมื่อถึงจุดหนึ่งต้องมีการจัดการบางอย่างพร้อมกัน จะเกิดอะไรขึ้นถ้าฟังก์ชันของฉันซิงโครไนซ์โดยเจตนาเนื่องจากมันจัดการเธรดของผู้ปฏิบัติงานที่รันเป็นเวลานานด้วยTask.Run()? ฉันจะรอให้เสร็จสิ้นโดยไม่มีการหยุดชะงักในการทดสอบ NUnit ของฉันได้อย่างไร
void.pointer

1
@ void.pointer: At some point, something has to be managed synchronously.- ไม่เลย สำหรับแอป UI จุดเข้าใช้งานสามารถเป็นasync voidตัวจัดการเหตุการณ์ได้ สำหรับแอปเซิร์ฟเวอร์จุดเข้าใช้งานอาจเป็นการasync Task<T>กระทำ ควรใช้asyncทั้งสองอย่างเพื่อหลีกเลี่ยงการบล็อกเธรด คุณสามารถให้การทดสอบ NUnit เป็นแบบซิงโครนัสหรือไม่ซิงค์ ถ้า async ให้มันแทนasync Task async voidหากเป็นแบบซิงโครนัสไม่ควรมีSynchronizationContextดังนั้นไม่ควรมีการหยุดชะงัก
Stephen Cleary

223

การรับค่าผ่านวิธี async:

var result = Task.Run(() => asyncGetValue()).Result;

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

Task.Run( () => asyncMethod()).Wait();

จะไม่มีปัญหาการหยุดชะงักเนื่องจากการใช้งานเรียกใช้


15
-1 เพื่อสนับสนุนให้ใช้async voidวิธีการทดสอบหน่วยและลบการค้ำประกันเธรดเดียวกันที่จัดทำโดยSynchronizationContextระบบที่อยู่ระหว่างการทดสอบ
Stephen Cleary

68
@StephenCleary: ไม่มี "enouraging" ของโมฆะ async เป็นเพียงการใช้โครงสร้าง c # ที่ถูกต้องเพื่อแก้ปัญหาการชะงักงัน ตัวอย่างข้อมูลข้างต้นเป็นวิธีแก้ไขปัญหาของ OP ที่ขาดไม่ได้และเรียบง่าย Stackoverflow เป็นเรื่องเกี่ยวกับการแก้ปัญหาไม่ใช่การโปรโมตตนเองอย่างละเอียด
Herman Schoenfeld

81
@StephenCleary: บทความของคุณไม่ได้อธิบายวิธีแก้ปัญหาอย่างชัดเจน (อย่างน้อยก็ไม่ชัดเจน) และแม้ว่าคุณจะมีวิธีแก้ปัญหาคุณก็จะใช้โครงสร้างดังกล่าวโดยอ้อม โซลูชันของฉันไม่ได้ใช้บริบทอย่างชัดเจนแล้วไงล่ะ ประเด็นคือของฉันใช้งานได้และเป็นซับเดียว ไม่จำเป็นต้องมีบล็อกโพสต์สองรายการและคำหลายพันคำในการแก้ปัญหา หมายเหตุ: ฉันไม่ได้ใช้async voidดังนั้นฉันจึงไม่รู้ว่าคุณกำลังทำอะไรอยู่ .. คุณเห็น "async void" ตรงไหนในคำตอบที่กระชับและเหมาะสมของฉันหรือไม่?
Herman Schoenfeld

15
@HermanSchoenfeld ถ้าคุณเพิ่มเหตุผลในวิธีการฉันเชื่อว่าคำตอบของคุณจะเป็นประโยชน์มาก
ironstone13

19
ฉันรู้ว่านี่เป็นเรื่องที่ล่าช้า แต่คุณควรใช้.GetAwaiter().GetResult()แทน.Resultเพื่อExceptionไม่ให้ห่อใด ๆ
Camilo Terevinto

15

คุณสามารถหลีกเลี่ยงการหยุดชะงักเพิ่มConfigureAwait(false)ในบรรทัดนี้:

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);

ฉันได้อธิบายข้อผิดพลาดนี้ไว้ในบล็อกโพสต์ของฉันPitfalls of async / await


9

คุณกำลังบล็อก UI โดยใช้คุณสมบัติ Task.Result ในเอกสาร MSDNได้กล่าวไว้อย่างชัดเจนว่า

" คุณสมบัติผลลัพธ์คือคุณสมบัติการบล็อกหากคุณพยายามเข้าถึงก่อนที่ภารกิจจะเสร็จสิ้นเธรดที่แอ็คทีฟอยู่ในขณะนี้จะถูกบล็อกจนกว่างานจะเสร็จสมบูรณ์และค่าจะพร้อมใช้งานในกรณีส่วนใหญ่คุณควรเข้าถึงค่าโดยใช้Awaitหรือรอแทนที่จะเข้าถึงทรัพย์สินโดยตรง "

ทางออกที่ดีที่สุดสำหรับสถานการณ์นี้คือการลบ a wait & asyncออกจากวิธีการและใช้เฉพาะTaskที่คุณส่งคืนผลลัพธ์ มันจะไม่ยุ่งกับลำดับการดำเนินการของคุณ


3

หากคุณไม่ได้รับการโทรกลับหรือตัวควบคุมวางสายหลังจากเรียกใช้ฟังก์ชัน async service / API คุณต้องกำหนดค่าบริบทเพื่อส่งคืนผลลัพธ์ในบริบทที่เรียกว่าเดียวกัน

ใช้ TestAsync().ConfigureAwait(continueOnCapturedContext: false);

คุณจะต้องเผชิญกับปัญหานี้เพียง แต่ในการใช้งานเว็บ static void mainแต่ไม่ได้อยู่ใน


ConfigureAwaitหลีกเลี่ยงการชะงักงันในบางสถานการณ์โดยการไม่รันในบริบทเธรดดั้งเดิม
davidcarr
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.