Aracılığıyla paylaş


Zaman uyumsuz dönüş türleri (C#)

Asenkron yöntemler aşağıdaki dönüş türlerine sahip olabilir:

  • Task, bir işlem gerçekleştiren ancak değer döndürmeyen zaman uyumsuz bir yöntem için.
  • Eşzamansız bir yöntem için, bir değer döndüren Task<TResult>.
  • voidbir olay işleyicisi için.
  • Erişilebilir bir GetAwaiter metodu olan herhangi bir tür. GetAwaiter yöntemi tarafından döndürülen nesnenin System.Runtime.CompilerServices.ICriticalNotifyCompletion arabirimini uygulaması gerekir.
  • zaman uyumsuz bir akışdöndüren bir zaman uyumsuz yöntem için .

Zaman uyumsuz yöntemler hakkında daha fazla bilgi için bkz. async ve await (C#) ile zaman uyumsuz programlama .

Windows iş yüklerine özgü başka türler de vardır:

Görev dönüş türü

return deyimi içermeyen veya işlenen döndürmeyen bir return deyimine sahip olan zaman uyumsuz yöntemler genellikle Taskdönüş tipine sahiptir. Bu tür yöntemler zaman uyumlu olarak çalıştırılırsa void döndürür. Zaman uyumsuz bir yöntem için Task dönüş türü kullanırsanız, çağıran yöntem, çağrılan zaman uyumsuz yöntem bitene kadar çağıranın tamamlanmasını askıya almak üzere bir await işleci kullanabilir.

Aşağıdaki örnekte, WaitAndApologizeAsync yöntemi bir return deyimi içermediğinden, yöntem bir Task nesnesi döndürür. Task döndürülerek WaitAndApologizeAsync beklenebilir. Task türü, dönüş değeri olmadığından bir Result özelliği içermez.

public static async Task DisplayCurrentInfoAsync()
{
    await WaitAndApologizeAsync();

    Console.WriteLine($"Today is {DateTime.Now:D}");
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Console.WriteLine("The current temperature is 76 degrees.");
}

static async Task WaitAndApologizeAsync()
{
    await Task.Delay(2000);

    Console.WriteLine("Sorry for the delay...\n");
}
// Example output:
//    Sorry for the delay...
//
// Today is Monday, August 17, 2020
// The current time is 12:59:24.2183304
// The current temperature is 76 degrees.

WaitAndApologizeAsync, senkron void döndüren metot için çağrı ifadesine benzer bir await ifadesi yerine await deyimi kullanılarak beklenir. Bu durumda await işlecinin kullanılması bir değer üretmez. Bir await'ın sağ işleneni bir Task<TResult>olduğunda, await ifadesi Tsonucunu verir. Ne zaman bir await’ın sağ işleneni bir Taskise, await ve onun işleneni bir deyimi oluşturur.

Aşağıdaki kodda gösterildiği gibi, WaitAndApologizeAsync çağrısını await işlecinin uygulamasından ayırabilirsiniz. Ancak, bir Task'ın Result özelliği olmadığını ve await operatörü bir Task'ye uygulandığında değer üretilmediğini unutmayın.

Aşağıdaki kod, WaitAndApologizeAsync yöntemini çağırmayı yöntemin döndürdüğü görevi beklemesinden ayırır.

Task waitAndApologizeTask = WaitAndApologizeAsync();

string output =
    $"Today is {DateTime.Now:D}\n" +
    $"The current time is {DateTime.Now.TimeOfDay:t}\n" +
    "The current temperature is 76 degrees.\n";

await waitAndApologizeTask;
Console.WriteLine(output);

Görev<TResult> dönüş türü

Task<TResult> dönüş türü, işlenenin olduğu TResult deyimini içeren eşzamansız bir yöntem için kullanılır.

Aşağıdaki örnekte, GetLeisureHoursAsync yöntemi tamsayı döndüren bir return deyimi içerir. Yöntem bildirimi, Task<int>dönüş türünü belirtmelidir. FromResult zaman uyumsuz yöntemi, DayOfWeekdöndüren bir işlem için yer tutucudur.

public static async Task ShowTodaysInfoAsync()
{
    string message =
        $"Today is {DateTime.Today:D}\n" +
        "Today's hours of leisure: " +
        $"{await GetLeisureHoursAsync()}";

    Console.WriteLine(message);
}

static async Task<int> GetLeisureHoursAsync()
{
    DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);

    int leisureHours =
        today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
        ? 16 : 5;

    return leisureHours;
}
// Example output:
//    Today is Wednesday, May 24, 2017
//    Today's hours of leisure: 5

GetLeisureHoursAsync yöntemindeki bir await ifadesinin içinden ShowTodaysInfo çağrıldığında, await ifadesi leisureHours yöntemi tarafından döndürülen görevde depolanan tamsayı değerini (GetLeisureHoursdeğeri) alır. await ifadesiyle ilgili daha detaylı bilgi almak için bkz. await.

Aşağıdaki kodun gösterdiği gibi, await çağrısını Task<T>uygulamasından ayırarak, GetLeisureHoursAsync'ın await sonucunu nasıl elde ettiğini daha iyi anlayabilirsiniz. Henüz beklenmeyen bir yöntem GetLeisureHoursAsync çağrısı, yöntemin bildiriminden bekleneceği üzere bir Task<int>döndürür. Görev örnekteki getLeisureHoursTask değişkenine atanır. getLeisureHoursTask bir Task<TResult>olduğundan, Resulttüründe bir TResult özelliği içerir. Bu durumda, TResult bir tamsayı türünü temsil eder. await getLeisureHoursTask'e uygulandığında, await ifadesi Result'ün getLeisureHoursTask özelliğinin içeriği ile eşleşir. Değer ret değişkenine atanır.

Önemli

Result özelliği bir engelleme özelliğidir. Görevin tamamlanması sona ermeden önce erişmeye çalışırsanız, şu anda aktif olan thread görev tamamlanana ve değer elde edilene kadar bloke olur. Çoğu durumda, özelliğine doğrudan erişmek yerine await kullanarak değere erişmeniz gerekir.

Önceki örnek, Result yönteminin uygulama sona ermeden önce Main'yi konsola yazdırabilmesi için ana iş parçacığını engelleyen message özelliğinin değerini aldı.

var getLeisureHoursTask = GetLeisureHoursAsync();

string message =
    $"Today is {DateTime.Today:D}\n" +
    "Today's hours of leisure: " +
    $"{await getLeisureHoursTask}";

Console.WriteLine(message);

Geçersiz dönüş türü

void dönüş türünü, void dönüş türü gerektiren zaman uyumsuz olay işleyicilerinde kullanırsınız. Değer döndürmeyen olay işleyicileri dışındaki yöntemler için bunun yerine bir Task döndürmelisiniz çünkü void döndüren zaman uyumsuz bir yöntem beklenemez. Böyle bir yöntemi çağıranlar, çağrılan eşzamansız yöntemin bitmesini beklemeden işlemlerini tamamlamalıdır. Çağıran, zaman uyumsuz yöntemin oluşturduğu herhangi bir değerden veya özel durumdan bağımsız olmalıdır.

Void döndüren bir async yöntemi çağıran kişi, yöntem tarafından atılan özel durumları yakalayamaz. Bu tür işlenmeyen özel durumlar uygulamanızın başarısız olmasına neden olabilir. Task veya Task<TResult> döndüren bir yöntem özel durum oluşturursa, özel durum döndürülen görevde depolanır. Görev beklenirken özel durum yeniden oluşturulur. Özel durum oluşturabilen herhangi bir asenkron yöntemin Task veya Task<TResult> dönüş türüne sahip olduğunu ve yöntem çağrılarının beklendiğinden emin olun.

Aşağıdaki örnek, zaman uyumsuz olay işleyicisinin davranışını gösterir. Örnek kodda, zaman uyumsuz bir etkinlik işleyicisi bittiğinde bunu ana iş parçacığına bildirmesi gerekir. Ardından ana iş parçacığı, programdan çıkmadan önce bir zaman uyumsuz olay işleyicisinin tamamlanmasını bekleyebilir.

public class NaiveButton
{
    public event EventHandler? Clicked;

    public void Click()
    {
        Console.WriteLine("Somebody has clicked a button. Let's raise the event...");
        Clicked?.Invoke(this, EventArgs.Empty);
        Console.WriteLine("All listeners are notified.");
    }
}

public class AsyncVoidExample
{
    static readonly TaskCompletionSource<bool> s_tcs = new TaskCompletionSource<bool>();

    public static async Task MultipleEventHandlersAsync()
    {
        Task<bool> secondHandlerFinished = s_tcs.Task;

        var button = new NaiveButton();

        button.Clicked += OnButtonClicked1;
        button.Clicked += OnButtonClicked2Async;
        button.Clicked += OnButtonClicked3;

        Console.WriteLine("Before button.Click() is called...");
        button.Click();
        Console.WriteLine("After button.Click() is called...");

        await secondHandlerFinished;
    }

    private static void OnButtonClicked1(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 1 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 1 is done.");
    }

    private static async void OnButtonClicked2Async(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 2 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 2 is about to go async...");
        await Task.Delay(500);
        Console.WriteLine("   Handler 2 is done.");
        s_tcs.SetResult(true);
    }

    private static void OnButtonClicked3(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 3 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 3 is done.");
    }
}
// Example output:
//
// Before button.Click() is called...
// Somebody has clicked a button. Let's raise the event...
//    Handler 1 is starting...
//    Handler 1 is done.
//    Handler 2 is starting...
//    Handler 2 is about to go async...
//    Handler 3 is starting...
//    Handler 3 is done.
// All listeners are notified.
// After button.Click() is called...
//    Handler 2 is done.

Genelleştirilmiş zaman uyumsuz dönüş türleri ve ValueTask<TResult>

Zaman uyumsuz yöntem,bir awaiter türünün örneğini döndüren erişilebilir bir yöntemine sahip herhangi bir türü döndürebilir. Ayrıca, GetAwaiter yönteminden döndürülen tür System.Runtime.CompilerServices.AsyncMethodBuilderAttribute özniteliğine sahip olmalıdır. Derleyici tarafından okunan Öznitelikleri makalesinde veya Görev türü oluşturucu deseni için C# belirtimidaha fazla bilgi edinebilirsiniz.

Bu özellik, beklenebilir ifadelerin tamamlayıcısıdır ve, awaitişleneninin gereksinimlerini açıklar. Genelleştirilmiş zaman uyumsuz dönüş türleri, derleyicinin farklı türler döndüren async yöntemler oluşturmasını sağlar. Genelleştirilmiş zaman uyumsuz dönüş türleri .NET kitaplıklarında performans geliştirmelerini etkinleştirdi. Task ve Task<TResult> başvuru türleri olduğundan, özellikle de ayırmalar sıkı döngülerde gerçekleştiğinde performans açısından kritik yollarda bellek ayırma performansı olumsuz etkileyebilir. Genelleştirilmiş dönüş türleri desteği, daha fazla bellek ayırmasını önlemek için başvuru türü yerine basit bir değer türü döndürebileceğiniz anlamına gelir.

.NET, genelleştirilmiş bir görev döndüren değerin basit bir uygulaması olarak System.Threading.Tasks.ValueTask<TResult> yapısını sağlar. Aşağıdaki örnek, iki zar atma işleminin değerini almak için ValueTask<TResult> yapısını kullanır.

class Program
{
    static readonly Random s_rnd = new Random();

    static async Task Main() =>
        Console.WriteLine($"You rolled {await GetDiceRollAsync()}");

    static async ValueTask<int> GetDiceRollAsync()
    {
        Console.WriteLine("Shaking dice...");

        int roll1 = await RollAsync();
        int roll2 = await RollAsync();

        return roll1 + roll2;
    }

    static async ValueTask<int> RollAsync()
    {
        await Task.Delay(500);

        int diceRoll = s_rnd.Next(1, 7);
        return diceRoll;
    }
}
// Example output:
//    Shaking dice...
//    You rolled 8

Genelleştirilmiş bir asenkron dönüş türü yazmak gelişmiş bir senaryodur ve özel ortamlarda kullanılmak üzere tasarlanmıştır. Bunun yerine Task, Task<T>ve ValueTask<T> türlerini kullanmayı göz önünde bulundurun; bu, zaman uyumsuz koda yönelik çoğu senaryoyu kapsar.

Belirtilen türün oluşturucusunu geçersiz kılmak için AsyncMethodBuilder özniteliğini zaman uyumsuz bir yönteme (zaman uyumsuz dönüş türü bildirimi yerine) uygulayabilirsiniz. Bu özniteliği genellikle .NET çalışma zamanında sağlanan farklı bir oluşturucuyu kullanmak için uygularsınız.

IAsyncEnumerable<T> ile eşzamansız akışlar

Bir zaman uyumsuz yöntem, zaman uyumsuz akışdöndürebilir ve bu, IAsyncEnumerable<T>tarafından temsil edilir. Asenkron bir akış, öğelerin tekrar eden asenkron çağrılarla öbekler halinde üretildiği durumlarda, bir akıştan okunan öğeleri listelemek için bir yol sağlar. Aşağıdaki örnek, eşzamansız bir akış üreten bir eşzamansız yöntemi göstermektedir.

static async IAsyncEnumerable<string> ReadWordsFromStreamAsync()
{
    string data =
        @"This is a line of text.
              Here is the second line of text.
              And there is one more for good measure.
              Wait, that was the penultimate line.";

    using var readStream = new StringReader(data);

    string? line = await readStream.ReadLineAsync();
    while (line != null)
    {
        foreach (string word in line.Split(' ', StringSplitOptions.RemoveEmptyEntries))
        {
            yield return word;
        }

        line = await readStream.ReadLineAsync();
    }
}

Yukarıdaki örnek, bir dizedeki satırları asenkron olarak okur. Her satır okunduktan sonra, kod dizedeki her sözcüğü numaralandırır. Arayanlar, await foreach deyimini kullanarak her sözcüğü numaralandırır. Yöntem, kaynak dizeden bir sonraki satırı eşzamanlı olmayan bir şekilde okuması gerektiğinde bekler.

Ayrıca bkz.