ทำไมฉันไม่สามารถใช้โอเปอเรเตอร์ 'ที่รอ' อยู่ภายในเนื้อความของคำสั่งล็อกได้


348

ไม่อนุญาตให้ใช้คำสำคัญที่รอใน C # (. NET Async CTP)

จากMSDN :

ไม่สามารถใช้นิพจน์ที่รออยู่ในฟังก์ชันซิงโครนัสในนิพจน์แบบสอบถามใน catch หรือ block สุดท้ายของคำสั่งการจัดการข้อยกเว้นในบล็อกคำสั่งล็อกหรือในบริบทที่ไม่ปลอดภัย

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

ฉันพยายามแก้ไขด้วยคำสั่งการใช้:

class Async
{
    public static async Task<IDisposable> Lock(object obj)
    {
        while (!Monitor.TryEnter(obj))
            await TaskEx.Yield();

        return new ExitDisposable(obj);
    }

    private class ExitDisposable : IDisposable
    {
        private readonly object obj;
        public ExitDisposable(object obj) { this.obj = obj; }
        public void Dispose() { Monitor.Exit(this.obj); }
    }
}

// example usage
using (await Async.Lock(padlock))
{
    await SomethingAsync();
}

อย่างไรก็ตามสิ่งนี้ไม่ทำงานตามที่คาดไว้ การเรียกใช้ Monitor.Exit ภายใน ExitDisposable.Dispose ดูเหมือนว่าจะบล็อกไปเรื่อย ๆ ฉันสงสัยว่าความไม่น่าเชื่อถือของการทำงานของฉันและเหตุผลที่รองบไม่ได้รับอนุญาตในคำสั่งล็อคที่เกี่ยวข้องอย่างใด

ไม่มีใครรู้ว่าทำไมรอไม่ได้รับอนุญาตภายในเนื้อหาของคำสั่งล็อค?


27
ฉันคิดว่าคุณพบเหตุผลที่ไม่อนุญาต
asawyer

3
ฉันขอแนะนำลิงก์นี้: hanselman.com/blog/…และอันนี้: blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx
hans

ฉันเพิ่งเริ่มเรียนรู้เพิ่มเติมเกี่ยวกับการเขียนโปรแกรม async หลังจากการหยุดชะงักจำนวนมากในแอปพลิเคชัน wpf ของฉันฉันพบว่าบทความนี้เป็นระบบป้องกันที่ดีในการเขียนโปรแกรมแบบ async msdn.microsoft.com/en-us/magazine/ ......
C. Tewalt

ล็อคได้รับการออกแบบมาเพื่อป้องกันการเข้าถึง async เมื่อการเข้าถึง async จะทำลายรหัสของคุณดังนั้นถ้าคุณใช้ async ในการล็อคของคุณทำให้การล็อคของคุณไม่ถูกต้อง .. ดังนั้นหากคุณต้องการรอบางสิ่งภายในล็อคคุณไม่ได้ใช้ล็อคอย่างถูกต้อง
MikeT

คำตอบ:


366

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

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

โทรไปที่ Monitor.Exit ภายใน ExitDisposable.Dispose ดูเหมือนว่าจะบล็อกไปเรื่อย ๆ ฉันสงสัยว่าความไม่น่าเชื่อถือของการทำงานของฉันและเหตุผลที่รองบไม่ได้รับอนุญาตในคำสั่งล็อคที่เกี่ยวข้องอย่างใด

ถูกต้องคุณได้ค้นพบว่าทำไมเราถึงทำให้มันผิดกฎหมาย การรออยู่ในล็อคเป็นสูตรสำหรับการผลิตเดดล็อก

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

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

ฉันทราบว่ามันยังเป็น "การปฏิบัติที่เลวร้ายที่สุด" ที่จะทำyield returnภายในlockด้วยเหตุผลเดียวกัน มันถูกกฎหมายที่จะทำ แต่ฉันหวังว่าเราจะทำให้มันผิดกฎหมาย เราจะไม่ทำผิดพลาดแบบเดียวกันกับ "รอ"


190
คุณจะจัดการสถานการณ์ที่คุณต้องการส่งคืนรายการแคชได้อย่างไรและหากรายการไม่มีคุณต้องคำนวณแบบอะซิงโครนัสเนื้อหาจากนั้นเพิ่ม + ส่งคืนรายการทำให้แน่ใจว่าไม่มีใครโทรหาคุณในระหว่างนี้?
Softlion

9
ฉันรู้ว่าฉันมางานปาร์ตี้ที่นี่ช้า แต่ฉันก็ประหลาดใจเมื่อเห็นว่าคุณใส่เด๊ดล็อกเป็นเหตุผลหลักว่าทำไมมันถึงเป็นความคิดที่ไม่ดี ฉันได้ข้อสรุปในความคิดของฉันเองว่าลักษณะของการล็อก / มอนิเตอร์ที่เข้ามาใหม่จะเป็นปัญหาที่ใหญ่กว่า นั่นคือคุณจัดคิวงานสองงานให้กับเธรดพูลที่ล็อก () ซึ่งในโลกแบบซิงโครนัสจะทำงานบนเธรดแยก แต่ตอนนี้ด้วยการรอ (ถ้าได้รับอนุญาตฉันหมายถึง) คุณอาจมีสองงานที่ดำเนินการภายในบล็อคล็อคเนื่องจากเธรดถูกใช้ซ้ำ ความฮือฮาตามมา หรือฉันเข้าใจผิดบางอย่าง?
Gareth Wilson

4
@GarethWilson: ฉันพูดคุยเกี่ยวกับการติดตายเพราะคำถามที่ถามเป็นเรื่องเกี่ยวกับการติดตาย คุณถูกต้องว่าปัญหาเรื่องการเข้าห้องน้ำซ้ำที่แปลกประหลาดเป็นไปได้และดูเหมือนจะเป็นไปได้
Eric Lippert

11
@Eric Lippert เนื่องจากSemaphoreSlim.WaitAsyncคลาสนั้นถูกเพิ่มในกรอบงาน. NET ได้เป็นอย่างดีหลังจากที่คุณโพสต์คำตอบนี้ฉันคิดว่าเราสามารถสรุปได้อย่างปลอดภัยว่าเป็นไปได้ตอนนี้ ความคิดเห็นของคุณเกี่ยวกับความยากลำบากในการใช้โครงสร้างดังกล่าวยังคงสมบูรณ์
Contango

7
"โค้ดที่กำหนดเองทำงานระหว่างเวลาที่การรอส่งคืนการควบคุมไปยังผู้เรียกและวิธีการดำเนินการต่อ" - แน่นอนว่านี่เป็นความจริงของรหัสใด ๆ แม้ในกรณีที่ไม่มี async / รออยู่ในบริบทแบบมัลติเธรด: เธรดอื่น ๆ เวลาและพูดรหัสตามอำเภอใจในขณะที่คุณพูดว่า "อาจนำการล็อกที่ผลิตการเรียงลำดับการล็อกการล็อกและดังนั้นจึงเป็นการหยุดชะงัก" เหตุใดจึงมีความสำคัญเป็นพิเศษกับ async / คอย? ฉันเข้าใจจุดที่สองอีกครั้งว่า "โค้ดสามารถดำเนินการต่อในเธรดอื่น" ซึ่งมีความสำคัญเป็นพิเศษกับ async / await
บาคาร่า

291

ใช้SemaphoreSlim.WaitAsyncวิธีการ

 await mySemaphoreSlim.WaitAsync();
 try {
     await Stuff();
 } finally {
     mySemaphoreSlim.Release();
 }

10
เมื่อเร็ว ๆ นี้วิธีการนี้ถูกนำมาใช้ใน. NET Framework ฉันคิดว่าเราสามารถสันนิษฐานได้ว่าแนวคิดของการล็อกใน async / ที่รอคอยโลกได้รับการพิสูจน์แล้ว
Contango

5
สำหรับข้อมูลเพิ่มเติมค้นหาข้อความ "SemaphoreSlim" ในบทความนี้: Async / Await - วิธีปฏิบัติที่ดีที่สุดในการเขียนโปรแกรมแบบอะซิงโครนัส
BobbyA

1
@JamesKo ถ้างานทุกคนกำลังรอคอยผลของStuffฉันไม่เห็นทางรอบใด ๆ ...
Ohad ไนเดอร์

7
มันไม่ควรจะเริ่มต้นได้เช่นเดียวmySemaphoreSlim = new SemaphoreSlim(1, 1)กับการทำงานเช่นlock(...)?
Sergey

3
เพิ่มเวอร์ชั่นเพิ่มเติมของคำตอบนี้: stackoverflow.com/a/50139704/1844247
Sergey

67

โดยทั่วไปมันจะเป็นสิ่งที่ผิดที่ต้องทำ

วิธีนี้สามารถนำมาใช้ได้สองวิธี:

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

  • ปลดล็อคในระหว่างการรอและ reququire มันเมื่อรอผลตอบแทน
    นี้ละเมิดหลักการของความประหลาดใจอย่างน้อย IMO ซึ่งวิธีการที่ไม่ตรงกันควรประพฤติอย่างใกล้ชิดที่เป็นไปได้เช่นรหัสซิงโครเทียบเท่า - เว้นแต่คุณจะใช้Monitor.Waitในการป้องกันการล็อคคุณคาดว่าจะ เป็นเจ้าของล็อคในช่วงระยะเวลาของบล็อก

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

// Now it's clear where the locks will be acquired and released
lock (foo)
{
}
var result = await something;
lock (foo)
{
}

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


5
เนื่องจากSemaphoreSlim.WaitAsyncคลาสนั้นถูกเพิ่มในกรอบงาน. NET ได้เป็นอย่างดีหลังจากที่คุณโพสต์คำตอบนี้ฉันคิดว่าเราสามารถสรุปได้อย่างปลอดภัยว่าเป็นไปได้ตอนนี้ ความคิดเห็นของคุณเกี่ยวกับความยากลำบากในการใช้โครงสร้างดังกล่าวยังคงสมบูรณ์
Contango

7
@Contango: ดีที่ไม่ได้ค่อนข้างสิ่งเดียวกัน โดยเฉพาะอย่างยิ่งสัญญาณไม่ได้เชื่อมโยงกับหัวข้อที่เฉพาะเจาะจง มันบรรลุเป้าหมายที่คล้ายกันเพื่อล็อค แต่มีความแตกต่างที่สำคัญ
Jon Skeet

@ JonSkeet ฉันรู้ว่านี่เป็นเธรดเก่า ๆ ที่น่าติดตาม แต่ฉันไม่แน่ใจว่าการโทรบางสิ่ง () ได้รับการปกป้องโดยใช้การล็อคเหล่านั้นในวิธีที่สอง? เมื่อเธรดกำลังดำเนินการบางสิ่ง () เธรดอื่น ๆ สามารถเข้าไปมีส่วนร่วมได้เช่นกัน! ฉันทำอะไรบางอย่างหายไปหรือเปล่า

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

41

นี่เป็นเพียงส่วนขยายของคำตอบนี้

using System;
using System.Threading;
using System.Threading.Tasks;

public class SemaphoreLocker
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public async Task LockAsync(Func<Task> worker)
    {
        await _semaphore.WaitAsync();
        try
        {
            await worker();
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

การใช้งาน:

public class Test
{
    private static readonly SemaphoreLocker _locker = new SemaphoreLocker();

    public async Task DoTest()
    {
        await _locker.LockAsync(async () =>
        {
            // [asyn] calls can be used within this block 
            // to handle a resource by one thread. 
        });
    }
}

1
มันอาจเป็นอันตรายได้ที่จะได้รับสัญญาณล็อคนอกtryบล็อก - หากมีข้อยกเว้นเกิดขึ้นระหว่างWaitAsyncและtryสัญญาณจะไม่ถูกปล่อยออกมา (หยุดชะงัก) ในทางกลับกันการย้ายการWaitAsyncเรียกเข้าไปในtryบล็อกจะแนะนำปัญหาอื่นเมื่อสัญญาณสามารถปล่อยได้โดยไม่ต้องล็อคได้รับ ดูหัวข้อที่เกี่ยวข้องที่อธิบายถึงปัญหานี้: stackoverflow.com/a/61806749/7889645
AndreyCh

16

สิ่งนี้อ้างถึงhttp://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/ , ที่เก็บแอพ Windows 8 และ. net 4.5

นี่คือมุมของฉันในเรื่องนี้:

คุณลักษณะภาษา async / await ทำให้หลายสิ่งค่อนข้างง่าย แต่ก็ยังแนะนำสถานการณ์ที่ไม่ค่อยพบก่อนที่จะใช้การโทรแบบ async: reentrance

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

นี่เป็นสถานการณ์จริงที่ฉันเจอในแอพ windows 8 App store: แอพของฉันมีสองเฟรม: การเข้าและออกจากเฟรมฉันต้องการโหลด / เซฟข้อมูลบางอย่างไปยังไฟล์ / ที่เก็บข้อมูล เหตุการณ์ OnNavigatedTo / From ถูกใช้สำหรับการบันทึกและการโหลด การบันทึกและการโหลดทำได้โดยฟังก์ชั่นยูทิลิตี้ async (เช่นhttp://winrtstoragehelper.codeplex.com/ ) เมื่อนำทางจากเฟรม 1 ถึงเฟรม 2 หรือในทิศทางอื่นโหลด async และการทำงานที่ปลอดภัยจะถูกเรียกและรอ ตัวจัดการเหตุการณ์กลายเป็น async ที่คืนค่าเป็นโมฆะ => ไม่สามารถรอได้

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

วิธีแก้ปัญหาขั้นต่ำสำหรับฉันคือรักษาความปลอดภัยการเข้าถึงไฟล์ผ่านการใช้และ AsyncLock

private static readonly AsyncLock m_lock = new AsyncLock();
...

using (await m_lock.LockAsync())
{
    file = await folder.GetFileAsync(fileName);
    IRandomAccessStream readStream = await file.OpenAsync(FileAccessMode.Read);
    using (Stream inStream = Task.Run(() => readStream.AsStreamForRead()).Result)
    {
        return (T)serializer.Deserialize(inStream);
    }
}

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

ที่นี่ คือโครงการทดสอบของฉัน: แอพ windows 8 app store ที่มีการทดสอบการโทรสำหรับรุ่นดั้งเดิมจากhttp://winrtstoragehelper.codeplex.com/และเวอร์ชันที่แก้ไขของฉันที่ใช้ AsyncLock จาก Stephen Toub http: //blogs.msdn co.th / b / pfxteam / เก็บ / 2012 / 12/02 / 10266988.aspxco.th

ฉันขอแนะนำลิงก์นี้ได้ไหม: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx


7

Stephen Taub ได้ใช้วิธีแก้ปัญหาสำหรับคำถามนี้ดูที่การสร้างแนวทางการประสานงาน Async ตอนที่ 7: AsyncReaderWriterLockAsyncReaderWriterLock

สตีเฟ่นเตาได้รับการยกย่องอย่างสูงในอุตสาหกรรมดังนั้นสิ่งที่เขาเขียนมีแนวโน้มที่จะเป็นของแข็ง

ฉันจะไม่ทำซ้ำรหัสที่เขาโพสต์ไว้ในบล็อกของเขา แต่ฉันจะแสดงวิธีใช้:

/// <summary>
///     Demo class for reader/writer lock that supports async/await.
///     For source, see Stephen Taub's brilliant article, "Building Async Coordination
///     Primitives, Part 7: AsyncReaderWriterLock".
/// </summary>
public class AsyncReaderWriterLockDemo
{
    private readonly IAsyncReaderWriterLock _lock = new AsyncReaderWriterLock(); 

    public async void DemoCode()
    {           
        using(var releaser = await _lock.ReaderLockAsync()) 
        { 
            // Insert reads here.
            // Multiple readers can access the lock simultaneously.
        }

        using (var releaser = await _lock.WriterLockAsync())
        {
            // Insert writes here.
            // If a writer is in progress, then readers are blocked.
        }
    }
}

หากคุณต้องการวิธีการที่ถูกนำเข้าสู่. NET Framework ให้ใช้SemaphoreSlim.WaitAsyncแทน คุณจะไม่ได้รับล็อคผู้อ่าน / นักเขียน แต่คุณจะได้ลองและทดสอบการใช้งาน


ฉันอยากรู้ว่ามีคำเตือนใด ๆ ที่ใช้รหัสนี้หรือไม่ หากใครสามารถสาธิตปัญหาใด ๆ กับรหัสนี้ฉันต้องการทราบ อย่างไรก็ตามสิ่งที่เป็นจริงคือแนวคิดของการล็อกแบบ async / await นั้นได้รับการพิสูจน์แล้วว่าดีในขณะที่SemaphoreSlim.WaitAsyncอยู่ใน. NET Framework รหัสทั้งหมดนี้จะเพิ่มแนวคิดล็อคอ่าน / เขียน
Contango

3

อืมดูเหมือนน่าเกลียดดูเหมือนว่าจะใช้งานได้

static class Async
{
    public static Task<IDisposable> Lock(object obj)
    {
        return TaskEx.Run(() =>
            {
                var resetEvent = ResetEventFor(obj);

                resetEvent.WaitOne();
                resetEvent.Reset();

                return new ExitDisposable(obj) as IDisposable;
            });
    }

    private static readonly IDictionary<object, WeakReference> ResetEventMap =
        new Dictionary<object, WeakReference>();

    private static ManualResetEvent ResetEventFor(object @lock)
    {
        if (!ResetEventMap.ContainsKey(@lock) ||
            !ResetEventMap[@lock].IsAlive)
        {
            ResetEventMap[@lock] =
                new WeakReference(new ManualResetEvent(true));
        }

        return ResetEventMap[@lock].Target as ManualResetEvent;
    }

    private static void CleanUp()
    {
        ResetEventMap.Where(kv => !kv.Value.IsAlive)
                     .ToList()
                     .ForEach(kv => ResetEventMap.Remove(kv));
    }

    private class ExitDisposable : IDisposable
    {
        private readonly object _lock;

        public ExitDisposable(object @lock)
        {
            _lock = @lock;
        }

        public void Dispose()
        {
            ResetEventFor(_lock).Set();
        }

        ~ExitDisposable()
        {
            CleanUp();
        }
    }
}

0

ฉันลองใช้ Monitor (รหัสด้านล่าง) ซึ่งดูเหมือนว่าจะใช้งานได้ แต่มี GOTCHA ... เมื่อคุณมีหลายเธรดมันจะให้ ... System.Threading.SynchronizationLockException

using System;
using System.Threading;
using System.Threading.Tasks;

namespace MyNamespace
{
    public class ThreadsafeFooModifier : 
    {
        private readonly object _lockObject;

        public async Task<FooResponse> ModifyFooAsync()
        {
            FooResponse result;
            Monitor.Enter(_lockObject);
            try
            {
                result = await SomeFunctionToModifyFooAsync();
            }
            finally
            {
                Monitor.Exit(_lockObject);
            }
            return result;
        }
    }
}

ก่อนหน้านี้ฉันเพิ่งทำสิ่งนี้ แต่มันอยู่ในตัวควบคุม ASP.NET ดังนั้นมันจึงทำให้เกิดการหยุดชะงัก

public async Task<FooResponse> ModifyFooAsync() { lock(lockObject) { return SomeFunctionToModifyFooAsync.Result; } }

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