Başlamayan bir Görevi Başka Bir Görevi Bekleyecek Nasıl Bildirilir?


9

Bu Birim Testini yaptım ve neden "Bekleyen Task.Delay ()" beklemiyor anlamıyorum!

   [TestMethod]
    public async Task SimpleTest()
    {
        bool isOK = false;
        Task myTask = new Task(async () =>
        {
            Console.WriteLine("Task.BeforeDelay");
            await Task.Delay(1000);
            Console.WriteLine("Task.AfterDelay");
            isOK = true;
            Console.WriteLine("Task.Ended");
        });
        Console.WriteLine("Main.BeforeStart");
        myTask.Start();
        Console.WriteLine("Main.AfterStart");
        await myTask;
        Console.WriteLine("Main.AfterAwait");
        Assert.IsTrue(isOK, "OK");
    }

Birim Test çıktısı:

Birim Test Çıkışı

Bu nasıl bir "beklemek" beklemiyor ve ana iş parçacığı devam ediyor?


Neyi başarmaya çalıştığınız biraz belirsiz. Beklediğiniz çıktıyı ekleyebilir misiniz?
OlegI

1
Test yöntemi çok açık - isOK'un doğru olması bekleniyor
Sir Rufo

Başlatılmamış bir görev oluşturmak için hiçbir neden yoktur. Görevler iş parçacığı değil, iş parçacığı kullanıyor . Ne yapmaya çalışıyorsun? Neden ilkinden Task.Run()sonra kullanmıyorsunuz Console.WriteLine?
Panagiotis Kanavos

1
@Elopo konularını yeni tarif ettiniz. Sen yok bir iş kuyruğunu uygulamaya görevleri havuz gerekir. Bir iş sırasına ihtiyacınız var, örneğin Action <T> nesneleri
Panagiotis Kanavos

2
@Elo yapmaya çalıştığınız şey .NET'te zaten kullanılabilir, örneğin ActionBlock gibi TPL Dataflow sınıfları veya daha yeni System.Threading.Channels sınıfları. Bir veya daha fazla eşzamanlı görevi kullanarak iletileri almak ve işlemek için bir ActionBlock oluşturabilirsiniz. Tüm bloklar yapılandırılabilir kapasiteye sahip giriş tamponlarına sahiptir. DOP ve kapasite, eşzamanlılığı, gaz kelebeği taleplerini kontrol etmenizi ve geri basınç uygulamanızı sağlar - çok fazla mesaj sıraya alınırsa, üretici bekliyor
Panagiotis Kanavos

Yanıtlar:


8

new Task(async () =>

Bir görev bir değil Func<Task>, bir Action. Eşzamansız yönteminizi çağırır ve döndüğünde bitmesini bekler. Ama öyle değil. Bir görev döndürür. Bu görev yeni görev tarafından beklenmiyor. Yeni görev için, yöntem döndürüldükten sonra iş yapılır.

Yeni bir göreve sarmak yerine zaten var olan görevi kullanmanız gerekir:

[TestMethod]
public async Task SimpleTest()
{
    bool isOK = false;

    Func<Task> asyncMethod = async () =>
    {
        Console.WriteLine("Task.BeforeDelay");
        await Task.Delay(1000);
        Console.WriteLine("Task.AfterDelay");
        isOK = true;
        Console.WriteLine("Task.Ended");
    };

    Console.WriteLine("Main.BeforeStart");
    Task myTask = asyncMethod();

    Console.WriteLine("Main.AfterStart");

    await myTask;
    Console.WriteLine("Main.AfterAwait");
    Assert.IsTrue(isOK, "OK");
}

4
Task.Run(async() => ... )da bir seçenektir
Sir Rufo


BTW bir myTask.Start();artıracakInvalidOperationException
Sir Rufo

@OlegI Görmüyorum. Lütfen açıklayabilir misiniz?
nvoigt

@nvoigt myTask.Start()Onun alternatifi için bir istisna getireceği anlamına gelir ve kullanırken kaldırılması gerektiğini varsayıyorum Task.Run(...). Çözümünüzde hata yok.
404

3

Sorun, Tasksonuç üretme amaçlı olmayan , genel olmayan sınıfı kullanmanızdır . Dolayısıyla, zaman Taskuyumsuz bir temsilci geçirerek örneği oluşturduğunuzda :

Task myTask = new Task(async () =>

... delege muamelesi görüyor async void. An async voidbir değil Task, beklenemez, istisnası ele alınamaz ve StackOverflow'da ve başka yerlerde sinirli programcılar tarafından yapılan binlerce sorunun kaynağıdır . Çözüm, genel Task<TResult>sınıfı kullanmaktır , çünkü bir sonuç döndürmek istiyorsunuz ve sonuç başka bir sonuçtur Task. Yani aşağıdakileri oluşturmanız gerekir Task<Task>:

Task<Task> myTask = new Task<Task>(async () =>

Şimdi Startdışta Task<Task>olduğunuzda neredeyse anında tamamlanacak çünkü işi sadece iç kısmı yaratmaktır Task. O zaman içini de beklemeniz gerekecek Task. Bu şekilde yapılabilir:

myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;

İki alternatifiniz var. İç kısım için açık bir referansa ihtiyacınız yoksa Task, dış kısmı Task<Task>iki kez bekleyebilirsiniz :

await await myTask;

... veya Unwrapdış ve iç görevleri bir araya getiren dahili uzantı yöntemini kullanabilirsiniz :

await myTask.Unwrap();

Bu kaydırma Task.Run, sıcak görevler oluşturan çok daha popüler yöntemi kullandığınızda otomatik olarak gerçekleşir , bu nedenle Unwrapgünümüzde çok sık kullanılmaz.

Zaman uyumsuz temsilcinizin bir sonuç, örneğin a sonucu döndürmesi gerektiğine karar verirseniz string, myTaskdeğişkeni tür olarak bildirmeniz gerekir Task<Task<string>>.

Not:Task Soğuk görevler oluşturmak için kurucuların kullanımını onaylamıyorum. Bir uygulama genellikle kaşlarını çattığından, gerçekten bilmiyorum nedenlerle, ama muhtemelen o kadar nadiren kullanıldığı için, diğer habersiz kullanıcıları / koruyucular / gözden geçirenleri kodu şaşırtıcı bir şekilde yakalama potansiyeline sahiptir.

Genel öneri: Bir yönteme argüman olarak zaman uyumsuz delege verirken dikkatli olun. Bu yöntem ideal olarak bir Func<Task>argüman (zaman uyumsuz delegeleri anlayan anlamına gelir) veya en azından bir Func<T>argüman beklemelidir (en azından üretilenin Taskgöz ardı edilmeyeceği anlamına gelir ). Bu yöntemin bir kabul ettiği talihsiz durumda, Actiondelege olarak kabul edilecektir async void. Bu nadiren istediğiniz şeydir.


Ne kadar ayrıntılı bir teknik cevap! teşekkür ederim.
Elo

@Elo benim zevk!
Theodor Zoulias

1
 [Fact]
        public async Task SimpleTest()
        {
            bool isOK = false;
            Task myTask = new Task(() =>
            {
                Console.WriteLine("Task.BeforeDelay");
                Task.Delay(3000).Wait();
                Console.WriteLine("Task.AfterDelay");
                isOK = true;
                Console.WriteLine("Task.Ended");
            });
            Console.WriteLine("Main.BeforeStart");
            myTask.Start();
            Console.WriteLine("Main.AfterStart");
            await myTask;
            Console.WriteLine("Main.AfterAwait");
            Assert.True(isOK, "OK");
        }

resim açıklamasını buraya girin


3
awaitGörev gecikmesi olmadan bu görevi gerçekten geciktirmeyeceğini anlıyorsunuz , değil mi? İşlevi kaldırdınız.
19:28, nvoigt

Az önce test ettim, @nvoigt haklı: Geçen süre: 0: 00: 00,0106554. Ve ekran görüntünüzde görüyoruz: "geçen süre: 18 ms",> = 1000 ms
Elo

Evet haklısın, cevabımı güncelliyorum. Tnx. Yorum yaptıktan sonra bunu minimal değişikliklerle çözüyorum. :)
BASKA

1
Wait () kullanmak iyi bir fikir ve bir Görev içinden beklemek değil! Teşekkürler !
Elo
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.