TaskCompletionSource <T> ne zaman kullanılmalıdır?


200

AFAIK, bildiği tek şey, bir noktada, maruz kaldığı özellik yoluyla tamamlanması için onun SetResultveya SetExceptionyönteminin çağrılmasıdır .Task<T>Task

Başka bir deyişle, a Task<TResult>ve tamamlanmasının üreticisi olarak hareket eder .

Burada örneği gördüm :

Bir Func'u eşzamansız olarak yürütmek ve bu işlemi temsil edecek bir Göreve sahip olmak için bir yola ihtiyacım varsa.

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

Ben olmasaydı hangi * kullanılabilir Task.Factory.StartNew- Ama ben do var Task.Factory.StartNew.

Soru:

Birisi örnekle ilgili bir senaryoyu lütfen açıklayabilir doğrudan karşı TaskCompletionSource bir karşı değil, varsayımsal bende olmayan hangi durum Task.Factory.StartNew?


5
TaskCompletionSource, temelde olay tabanlı zaman uyumsuz API'leri yeni Konular oluşturmadan Görev ile sarmak için kullanılır.
Arvis

Yanıtlar:


230

Çoğunlukla yalnızca olay tabanlı bir API kullanılabilir olduğunda kullanıyorum ( örneğin Windows Phone 8 soketleri ):

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

Bu nedenle, özellikle C # 5 asyncanahtar kelimesi ile birlikte kullanıldığında yararlıdır .


4
kelimelerle yazabilir misin burada ne görüyoruz? SomeApiWrapperyayıncı bu görevin tamamlanmasına neden olan olayı gündeme getirene kadar böyle bir yerde mi beklenir?
Royi Namir

az önce eklediğim bağlantıya bir göz at
GameScripting

6
Sadece bir güncelleme olan Microsoft, .NET 4.0 projelerinde (VS2012 ve üstü önerilir) anahtar kelimelere Microsoft.Bcl.Asyncizin veren async/awaitNuGet paketini yayınladı .
Erik

1
Fran_gg7 @ Bir CancellationToken kullanın görebiliyordu msdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspx veya stackoverflow burada yeni bir soru olarak
GameScripting

1
Bu uygulamadaki sorun, olay obj'den asla serbest bırakılmadığından bunun bir bellek sızıntısı oluşturmasıdır.
Walter Vehoeven

78

Deneyimlerime göre, TaskCompletionSourceeski asenkron desenleri modern async/awaitdesene sarmak için harika .

Düşünebileceğim en faydalı örnek, çalışırken Socket. Eski APM ve DAP desenleri, ancak sahiptir awaitable Taskyöntemleri TcpListenerve TcpClientvar.

Şahsen NetworkStreamsınıfla ilgili birkaç sorunum var ve ham olanı tercih ediyorum Socket. async/awaitDeseni de sevdiğim için SocketExtender, birkaç uzatma yöntemi oluşturan bir uzantı sınıfı yaptım Socket.

Bu yöntemlerin tümü TaskCompletionSource<T>, eşzamansız çağrıları şu şekilde sarmak için kullanılır :

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

Ben geçmesi socketiçine BeginAcceptben derleyici yerel parametresini dalgalandır zorunda değil dışarı hafif bir performans artışı elde böylece yöntemlerle.

Sonra her şeyin güzelliği:

 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 neden burada çalışmadı?
Tola Odejayi

23
@Tola Bu bir threadpool iş parçacığı üzerinde çalışan yeni bir görev oluşturmuş olurdu, ancak yukarıdaki kod BeginAccept tarafından başlatılan i / o tamamlama iş parçacığı kullanır, iow: yeni bir iş parçacığı başlatmaz.
Frans Bouma

4
Teşekkürler, @ Frans-Bouma. Bu yüzden TaskCompletionSource, Begin ... End ... ifadelerini kullanan bir kodu bir göreve dönüştürmenin kullanışlı bir yoludur?
Tola Odejayi

3
@TolaOdejayi Geç cevap biraz, ama evet bunun için bulduğum birincil kullanım durumlarından biri. Bu kod geçişi için harika çalışıyor.
Erik

4
Deyimleri sarmak için TaskFactory <TResult> .FromAsync dosyasına bakın Begin.. End....
MicBig

37

Bana göre, klasik bir senaryo, TaskCompletionSourceyöntemimin mutlaka zaman alıcı bir işlem yapması gerekmediği zamandır. Yapmamızı sağlayan şey, yeni bir iş parçacığı kullanmak istediğimiz belirli durumları seçmektir.

Bunun için iyi bir örnek önbellek kullanmanızdır. GetResourceAsyncİstenen kaynak için önbelleğe bakan TaskCompletionSourceve kaynak bulunursa bir kerede (yeni bir iş parçacığı kullanmadan ) kullanarak dönen bir yönteme sahip olabilirsiniz . Yalnızca kaynak bulunamazsa, yeni bir iş parçacığı kullanmak ve bunu kullanarak almak istiyoruz Task.Run().

Kod örneği burada görülebilir: Bir kodu görevler kullanılarak eşzamansız olarak çalıştırma


Sorunuzu ve yanıtı gördüm. (cevabın yorumuna bakın) .... :-) ve gerçekten de eğitici bir soru ve cevap.
Royi Namir

11
Bu aslında TCS'nin gerekli olduğu bir durum değildir. Bunu Task.FromResultyapmak için kullanabilirsiniz . Tabii ki, 4.0 kullanıyorsanız ve Task.FromResultTCS için kullanacağınız bir şey yoksa , kendiniz yazmaktır FromResult .
14'te

@Servy Task.FromResultyalnızca .NET 4.5'ten beri kullanılabilir. Ondan önce, bu davranışa ulaşmanın yolu buydu.
Adi Lester

@AdiLester Cevabınız 4.5+ olduğunu Task.Runbelirten referanstır . Önceki yorumum özellikle .NET 4.0'a yönelikti.
14'te

@Servy Bu yanıtı okuyan herkes .NET 4.5 ve sonraki sürümlerini hedeflemiyor. Bunun OP'nin sorusunu sormasına yardımcı olan iyi ve geçerli bir cevap olduğuna inanıyorum (bu arada .NET-4.0 olarak etiketlenmiştir). Her iki durumda da, küçümseme bana biraz fazla geliyor, ama gerçekten bir düşüşü hak ettiğini düşünüyorsanız, devam edin.
Adi Lester

25

Gelen bu blog yayınında , Levi Botelho nasıl kullanılacağı açıklanır TaskCompletionSourcebunu başlatmak ve fesih bekliyor şekilde bir Süreci için bir asenkron sarmalayıcı yazmak için.

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

ve kullanımı

await RunProcessAsync("myexecutable.exe");

14

Hiç kimsenin bahsetmediği gibi görünüyor, ama sanırım birim testleri de gerçek hayat kadar yeterli kabul edilebilir .

TaskCompletionSourceBir asenkron yöntemle bağımlılık alay ederken yararlı buluyorum .

Test edilen gerçek programda:

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

Birim testlerinde:

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

Sonuçta, bu TaskCompletionSource kullanımı "kod yürütmeyen bir Görev nesnesi" başka bir durum gibi görünüyor.


11

TaskCompletionSource kodu çalıştırmayan Görev nesneleri oluşturmak için kullanılır . Gerçek dünya senaryolarında, TaskCompletionSource I / O bağlantılı işlemler için idealdir. Bu şekilde, işlem süresince bir iş parçacığını engellemeden görevlerin tüm avantajlarından (örneğin, dönüş değerleri, devamlar vb.) Faydalanırsınız. "İşleviniz" bir G / Ç bağlantılı işlemse, yeni bir Görev kullanarak bir iş parçacığını engellemeniz önerilmez . Bunun yerine, TaskCompletionSource'u kullanarak , yalnızca G / Ç bağlı işleminizin ne zaman bittiğini veya hata yaptığını belirtmek için bir bağımlı görev oluşturabilirsiniz.


5

Bu yazıda ".NET ile Paralel Programlama" blogundan iyi bir açıklama içeren gerçek bir dünya örneği var . Gerçekten okumalısınız, ama yine de bir özet.

Blog yayını iki uygulama gösterir:

"" gecikmeli "görevler oluşturmak için bir fabrika yöntemi, kullanıcı tarafından sağlanan bazı zaman aşımı gerçekleşene kadar zamanlanmayacak olanlar."

Gösterilen ilk uygulama Task<>iki ana kusura dayanır ve buna sahiptir. İkinci uygulama postu bunları kullanarak hafifletmeye devam ediyor TaskCompletionSource<>.

İşte bu ikinci uygulama:

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 üzerinde beklemek ve sonra eylemi kullanmak daha iyi olurdu () sonra
Royi Namir

5
çünkü bıraktığınız yere geri dönersiniz, Continuewith içeriği korumaz. (varsayılan olarak değil) Ayrıca, eylemdeki bir sonraki ifade () bir istisnaya neden oluyorsa, await kullanmanın sizi normal bir istisna olarak göstereceği yerde yakalamak zor olacaktır.
Royi Namir

3
Neden sadece await Task.Delay(millisecondsDelay); action(); return;veya (.Net 4.0'da)return Task.Delay(millisecondsDelay).ContinueWith( _ => action() );
sgnsajgon

@sgnsajgon ki bu kesinlikle okunması ve bakımı daha kolay olurdu
JwJosefy

@JwJosefy Aslında, Task.Delay yöntemi, yukarıdaki koda benzer şekilde TaskCompletionSource kullanılarak uygulanabilir . Gerçek uygulama burada: Task.cs
sgnsajgon

4

Bu işleri basitleştiriyor olabilir, ancak TaskCompletion kaynağı kişinin bir olayı beklemesine izin verir. Tcs.SetResult yalnızca olay gerçekleştiğinde ayarlandığından, arayan görevde bekleyebilir.

Daha fazla bilgi için bu videoyu izleyin:

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


1
Bağlantılar zaman içinde değişebileceğinden ve bu yanıtı ilgisiz hale getirebileceğinden, lütfen ilgili kodu veya belgeleri buraya yerleştirin.
rfornal

3

Kullandığım gerçek dünya senaryosu TaskCompletionSourcebir indirme kuyruğu uygularken. Benim durumumda kullanıcı 100 indirme başlarsa ben hepsini bir kerede ateş etmek istemiyorum ve bu yüzden bir strated görev iade yerine bağlı bir görev döndürmek TaskCompletionSource. İndirme işlemi tamamlandığında, sıradaki iş parçacığı görevi tamamlar.

Buradaki temel kavram, bir müşteri gerçekten başladığı andan itibaren bir görevin başlatılmasını istediğinde ayrıştırıyorum. Bu durumda istemcinin kaynak yönetimi ile uğraşmasını istemiyorum.

C # 5 derleyicisini (VS 2012+) kullandığınız sürece .net 4'te async / await kullanabileceğinizi unutmayın . daha fazla ayrıntı için buraya bakın.


0

Ben kullandım TaskCompletionSourceiptal edilene kadar Görev çalıştırmak için. Bu durumda normalde uygulama çalıştığı sürece çalıştırmak istediğim bir ServiceBus abonesidir.

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
Zaten bir görev oluşturduğundan 'TaskCompletionSource' ile 'async' kullanmaya gerek yok
Mandeep Janjua

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.