เมื่อใดที่ควรใช้ TaskCompletionSource <T>


199

AFAIK ทั้งหมดก็รู้ก็คือว่าในบางจุดของมันSetResultหรือSetExceptionวิธีการที่จะถูกเรียกตัวไปดำเนินการTask<T>สัมผัสผ่านTaskคุณสมบัติ

กล่าวอีกนัยหนึ่งมันทำหน้าที่เป็นผู้อำนวยการสร้างTask<TResult>และเสร็จสมบูรณ์

ฉันเห็นตัวอย่างที่นี่ :

หากฉันต้องการวิธีในการรัน Func แบบอะซิงโครนัสและมีภารกิจเพื่อแสดงการดำเนินการนั้น

public static Task<T> RunAsync<T>(Func<T> function) 
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        {  
            T result = function(); 
            tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
}

ซึ่งสามารถนำมาใช้ * ถ้าฉันไม่ได้มีTask.Factory.StartNew- แต่ผมไม่Task.Factory.StartNewได้

คำถาม:

ใครจะกรุณาอธิบายโดยยกตัวอย่างเช่นสถานการณ์ที่เกี่ยวข้องโดยตรงจะTaskCompletionSource ไม่ไปสมมุติสถานการณ์ที่ผมไม่ได้ Task.Factory.StartNew?


5
TaskCompletionSource ส่วนใหญ่จะใช้สำหรับการตัดเหตุการณ์ตาม async api กับงานโดยไม่ต้องทำเธรดใหม่
Arvis

คำตอบ:


230

ฉันใช้เป็นส่วนใหญ่เมื่อมีเพียง API ที่ใช้เหตุการณ์เท่านั้น ( เช่นซ็อกเก็ต Windows Phone 8 ):

public Task<Args> SomeApiWrapper()
{
    TaskCompletionSource<Args> tcs = new TaskCompletionSource<Args>(); 

    var obj = new SomeApi();

    // will get raised, when the work is done
    obj.Done += (args) => 
    {
        // this will notify the caller 
        // of the SomeApiWrapper that 
        // the task just completed
        tcs.SetResult(args);
    }

    // start the work
    obj.Do();

    return tcs.Task;
}

ดังนั้นจึงมีประโยชน์อย่างยิ่งเมื่อใช้ร่วมกับasyncคำหลักC # 5


4
คุณสามารถเขียนด้วยคำที่เราเห็นที่นี่? มันเหมือนที่SomeApiWrapperรออยู่ที่ไหนสักแห่งจนกว่าผู้เผยแพร่จะเพิ่มเหตุการณ์ที่ทำให้งานนี้เสร็จสมบูรณ์หรือไม่
Royi Namir

ดูลิงค์ที่ฉันเพิ่งเพิ่ม
GameScripting

6
เพียงอัปเดต Microsoft ได้เปิดตัวMicrosoft.Bcl.Asyncแพ็คเกจบน NuGet ซึ่งอนุญาตให้ใช้async/awaitคำหลักในโครงการ. NET 4.0 (แนะนำให้ใช้ VS2012 หรือสูงกว่า)
Erik

1
@ Fran_gg7 คุณสามารถใช้ CancellationToken ดูmsdn.microsoft.com/en-us/library/dd997396(v=vsv. 1010 ).aspxหรือเป็นคำถามใหม่ที่นี่ใน stackoverflow
GameScripting

1
ปัญหาของการใช้งานนี้คือสิ่งนี้สร้างหน่วยความจำรั่วเนื่องจากเหตุการณ์ไม่เคยถูกปล่อยออกมาจาก obj.Done
Walter Vehoeven

78

ในประสบการณ์ของฉันTaskCompletionSourceเป็นที่ดีสำหรับการตัดรูปแบบไม่ตรงกันเก่าไปทันสมัยasync/awaitรูปแบบ

Socketตัวอย่างที่เป็นประโยชน์มากที่สุดที่ฉันสามารถคิดคือเมื่อทำงานร่วมกับ มันมีรูปแบบ APM และ EAP แบบเก่า แต่ไม่ใช่awaitable TaskวิธีการTcpListenerและTcpClientมี

ผมเองมีหลายประเด็นที่มีการเรียนและต้องการดิบNetworkStream Socketเป็นที่ฉันยังรักasync/awaitรูปแบบที่ผมทำชั้นนามสกุลซึ่งจะสร้างวิธีการขยายหลายSocketExtenderSocket

วิธีการทั้งหมดเหล่านี้ใช้ประโยชน์จากTaskCompletionSource<T>การตัดการโทรแบบอะซิงโครนัสดังนี้:

    public static Task<Socket> AcceptAsync(this Socket socket)
    {
        if (socket == null)
            throw new ArgumentNullException("socket");

        var tcs = new TaskCompletionSource<Socket>();

        socket.BeginAccept(asyncResult =>
        {
            try
            {
                var s = asyncResult.AsyncState as Socket;
                var client = s.EndAccept(asyncResult);

                tcs.SetResult(client);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }

        }, socket);

        return tcs.Task;
    }

ผมผ่านsocketเข้าไปในBeginAcceptวิธีการเพื่อที่ฉันได้รับการเพิ่มประสิทธิภาพการทำงานเล็กน้อยจากคอมไพเลอร์ไม่ต้องยกพารามิเตอร์ท้องถิ่น

จากนั้นความงามของมันทั้งหมด:

 var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 listener.Bind(new IPEndPoint(IPAddress.Loopback, 2610));
 listener.Listen(10);

 var client = await listener.AcceptAsync();

1
ทำไม Task.Factory.StartNew ถึงไม่ทำงานที่นี่?
Tola Odejayi

23
@Tola ตามที่จะสร้างงานใหม่ที่รันบนเธรดพูลเธรด แต่โค้ดด้านบนใช้เธรด i / o ที่สมบูรณ์ที่เริ่มต้นโดย BeginAccept, iow: มันไม่ได้เริ่มเธรดใหม่
ฟราน Bouma

4
ขอบคุณ @ Frans-Bouma ดังนั้น TaskCompletionSource เป็นวิธีที่สะดวกในการแปลงรหัสที่ใช้คำสั่ง Begin ... End ... ไปยังงานหรือไม่
Tola Odejayi

3
@TolaOdejayi บิตของการตอบกลับช้า แต่ใช่ว่าเป็นหนึ่งในกรณีการใช้งานหลักที่ฉันได้พบมัน มันทำงานได้อย่างมหัศจรรย์สำหรับการเปลี่ยนรหัสนี้
Erik

4
ดูที่TaskFactory <TResult> .FromAsyncเพื่อตัดBegin.. End...คำสั่ง
MicBig

37

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

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

ตัวอย่างรหัสสามารถเห็นได้ที่นี่: วิธีการเรียกใช้โค้ดแบบอะซิงโคนัลโดยใช้งาน


ฉันเห็นคำถามของคุณและคำตอบด้วย (ดูที่ความคิดเห็นของฉันเพื่อคำตอบ) .... :-) และแน่นอนมันเป็นคำถามและคำตอบที่ให้ความรู้
Royi Namir

11
นี่ไม่ใช่สถานการณ์ที่จำเป็นต้องใช้ TCS คุณสามารถใช้Task.FromResultเพื่อทำสิ่งนี้ แน่นอนถ้าคุณกำลังใช้ 4.0 และไม่ได้มีTask.FromResultสิ่งที่คุณต้องการใช้ TCS สำหรับคือการเขียนของคุณเอง FromResult
Servy

@Servy Task.FromResultมีให้บริการตั้งแต่. NET 4.5 ก่อนหน้านั้นเป็นวิธีที่จะทำให้เกิดพฤติกรรมนี้
Adi Lester

@AdiLester คำตอบของคุณคือการอ้างอิงTask.Runโดยระบุว่าเป็น 4.5+ และความคิดเห็นก่อนหน้าของฉันระบุเฉพาะ. NET 4.0
Servy

@Servy ไม่ใช่ทุกคนที่อ่านคำตอบนี้กำลังกำหนดเป้าหมาย. NET 4.5+ ฉันเชื่อว่านี่เป็นคำตอบที่ดีและถูกต้องที่ช่วยให้ผู้คนถามคำถาม OP (ซึ่งติดแท็ก. NET-4.0) ไม่ว่าจะด้วยวิธีใดการลง downvoting ดูเหมือนจะเป็นเรื่องเล็กน้อยสำหรับฉัน แต่ถ้าคุณเชื่อว่าสมควรได้รับ downvote คุณควรดำเนินการต่อไป
Adi Lester

25

ในโพสต์บล็อกนี้ Levi Botelho อธิบายถึงวิธีการใช้TaskCompletionSourceเพื่อเขียน wrapper แบบอะซิงโครนัสสำหรับกระบวนการเพื่อให้คุณสามารถเปิดใช้งานและรอการยกเลิก

public static Task RunProcessAsync(string processPath)
{
    var tcs = new TaskCompletionSource<object>();
    var process = new Process
    {
        EnableRaisingEvents = true,
        StartInfo = new ProcessStartInfo(processPath)
        {
            RedirectStandardError = true,
            UseShellExecute = false
        }
    };
    process.Exited += (sender, args) =>
    {
        if (process.ExitCode != 0)
        {
            var errorMessage = process.StandardError.ReadToEnd();
            tcs.SetException(new InvalidOperationException("The process did not exit correctly. " +
                "The corresponding error message was: " + errorMessage));
        }
        else
        {
            tcs.SetResult(null);
        }
        process.Dispose();
    };
    process.Start();
    return tcs.Task;
}

และการใช้งาน

await RunProcessAsync("myexecutable.exe");

14

ดูเหมือนว่าไม่มีใครพูดถึง แต่ฉันเดาว่าการทดสอบหน่วยก็อาจถือว่าเป็นชีวิตจริงได้เช่นกัน

ฉันพบว่าTaskCompletionSourceมีประโยชน์เมื่อเยาะเย้ยการพึ่งพาด้วยวิธีการ async

ในโปรแกรมจริงภายใต้การทดสอบ:

public interface IEntityFacade
{
  Task<Entity> GetByIdAsync(string id);
}

ในการทดสอบหน่วย:

// set up mock dependency (here with NSubstitute)

TaskCompletionSource<Entity> queryTaskDriver = new TaskCompletionSource<Entity>();

IEntityFacade entityFacade = Substitute.For<IEntityFacade>();

entityFacade.GetByIdAsync(Arg.Any<string>()).Returns(queryTaskDriver.Task);

// later on, in the "Act" phase

private void When_Task_Completes_Successfully()
{
  queryTaskDriver.SetResult(someExpectedEntity);
  // ...
}

private void When_Task_Gives_Error()
{
  queryTaskDriver.SetException(someExpectedException);
  // ...
}

ท้ายที่สุดการใช้งานของ TaskCompletionSource นี้ดูเหมือนจะเป็นอีกกรณีหนึ่งของ "วัตถุภารกิจที่ไม่ได้ใช้รหัส"


11

TaskCompletionSourceใช้เพื่อสร้างภารกิจของวัตถุที่ไม่ได้ใช้รหัส ในสถานการณ์โลกแห่งความเป็นจริงTaskCompletionSourceเหมาะอย่างยิ่งสำหรับการดำเนินการที่ถูกผูกไว้กับ I / O ด้วยวิธีนี้คุณจะได้รับประโยชน์ทั้งหมดของงาน (เช่นค่าส่งคืนความต่อเนื่อง ฯลฯ ) โดยไม่ปิดกั้นเธรดในช่วงระยะเวลาของการดำเนินการ หาก "ฟังก์ชั่น" ของคุณเป็น I / O การดำเนินงานที่ถูกผูกไว้จะไม่แนะนำเพื่อป้องกันด้ายใช้ใหม่งาน แต่ใช้TaskCompletionSource แทนคุณสามารถสร้างภารกิจทาสเพื่อระบุเมื่อการดำเนินการ I / O ของคุณเสร็จสิ้นหรือเกิดข้อผิดพลาด


5

มีตัวอย่างที่เป็นโลกแห่งความจริงกับคำอธิบายที่ดีในเรื่องนี้โพสต์จาก "การเขียนโปรแกรมแบบขนานกับ .NET" บล็อก คุณควรอ่านมันจริงๆ แต่นี่เป็นบทสรุป

โพสต์บล็อกแสดงสองการใช้งานสำหรับ:

"วิธีการจากโรงงานสำหรับการสร้างงาน" ล่าช้า "งานที่ไม่ได้ถูกกำหนดเวลาไว้จริงจนกว่าจะถึงเวลาที่ผู้ใช้กำหนด

การใช้งานครั้งแรกที่แสดงขึ้นอยู่กับTask<>และมีข้อบกพร่องหลักสองประการ TaskCompletionSource<>โพสต์การดำเนินงานที่สองไปในการบรรเทาเหล่านี้โดยใช้

นี่คือการใช้งานครั้งที่สอง:

public static Task StartNewDelayed(int millisecondsDelay, Action action)
{
    // Validate arguments
    if (millisecondsDelay < 0)
        throw new ArgumentOutOfRangeException("millisecondsDelay");
    if (action == null) throw new ArgumentNullException("action");

    // Create a trigger used to start the task
    var tcs = new TaskCompletionSource<object>();

    // Start a timer that will trigger it
    var timer = new Timer(
        _ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite);

    // Create and return a task that will be scheduled when the trigger fires.
    return tcs.Task.ContinueWith(_ =>
    {
        timer.Dispose();
        action();
    });
}

จะดีกว่าที่จะใช้คอย tcs.Task แล้วใช้การกระทำ () หลังจาก
Royi Namir

5
เพราะคุณกลับไปที่บริบทที่คุณจากไปโดยที่ Continuewith ไม่ได้เก็บบริบทไว้ (ไม่ใช่โดยค่าเริ่มต้น) หากคำสั่งถัดไปในการดำเนินการ () ทำให้เกิดข้อยกเว้นมันเป็นเรื่องยากที่จะจับมันในที่ที่การรอคอยจะแสดงให้คุณเห็นว่าเป็นข้อยกเว้นปกติ
Royi Namir

3
ทำไมไม่เพียงawait Task.Delay(millisecondsDelay); action(); return;หรือ (ใน. Net 4.0)return Task.Delay(millisecondsDelay).ContinueWith( _ => action() );
sgnsajgon

@sgnsajgon ที่จะอ่านและบำรุงรักษาได้ง่ายขึ้นอย่างแน่นอน
JwJosefy

@JwJosefy ที่จริงแล้ววิธีการTask.Delayสามารถดำเนินการได้โดยใช้TaskCompletionSourceคล้ายกับรหัสข้างต้น การใช้งานจริงอยู่ที่นี่: Task.cs
sgnsajgon

4

สิ่งนี้อาจทำให้สิ่งต่าง ๆ มีขนาดใหญ่เกินไป แต่แหล่ง TaskCompletion อนุญาตให้ใครรอเหตุการณ์ เนื่องจากมีการตั้งค่า tcs.SetResult เมื่อเหตุการณ์เกิดขึ้นผู้เรียกจึงสามารถรองานได้

ชมวิดีโอนี้เพื่อรับข้อมูลเชิงลึกเพิ่มเติม:

http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03-TipsForAsyncThreadsAndDatabinding


1
โปรดวางรหัสหรือเอกสารที่เกี่ยวข้องที่นี่เนื่องจากลิงก์สามารถเปลี่ยนแปลงได้ตลอดเวลาและทำให้คำตอบนี้ไม่เกี่ยวข้อง
rfornal

3

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

แนวคิดหลักที่นี่คือฉัน decoupling เมื่อลูกค้าขอให้งานที่จะเริ่มต้นเมื่อมันเริ่มจริง ในกรณีนี้เพราะฉันไม่ต้องการให้ลูกค้าต้องจัดการกับการจัดการทรัพยากร

โปรดทราบว่าคุณสามารถใช้ async / คอยใน. net 4 ตราบใดที่คุณใช้คอมไพเลอร์ C # 5 (VS 2012+) ดูที่นี่สำหรับรายละเอียดเพิ่มเติม


0

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

public async Task RunUntilCancellation(
    CancellationToken cancellationToken,
    Func<Task> onCancel)
{
    var doneReceiving = new TaskCompletionSource<bool>();

    cancellationToken.Register(
        async () =>
        {
            await onCancel();
            doneReceiving.SetResult(true); // Signal to quit message listener
        });

    await doneReceiving.Task.ConfigureAwait(false); // Listen until quit signal is received.
}

1
ไม่จำเป็นต้องใช้ 'async' กับ 'TaskCompletionSource' เพราะมันได้สร้างงานไปแล้ว
Mandeep Janjua

-1

TaskCompletionSourceคือ Tasks เช่นเดียวWaitHandleกับเธรด และเพื่อให้เราสามารถใช้ในการดำเนินการส่งสัญญาณได้อย่างแม่นยำTaskCompletionSource

ตัวอย่างคือคำตอบของฉันสำหรับคำถามนี้: ความล่าช้าของ ContentDialog หลังจากคลิกตกลง

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