Async Await Best Practices Cheat Sheet

Summary of Asynchronous Programming Guidelines

Name Description Exceptions
Avoid async void Prefer async Task methods over async void methods Event handlers
Async all the way Don’t mix blocking and async code Console main method
Configure context Use ConfigureAwait(false) when you can Methods that require con­text

The Async Way of Doing Things

To Do This … Instead of This … Use This
Retrieve the result of a background task Task.Wait or Task.Result await
Wait for any task to complete Task.WaitAny await Task.WhenAny
Retrieve the results of multiple tasks Task.WaitAll await Task.WhenAll
Wait a period of time Thread.Sleep await Task.Delay

Know Your Tools

There’s a lot to learn about async and await, and it’s natural to get a little disoriented. Here’s a quick reference of solutions to common problems.

Solutions to Common Async Problems

Problem Solution
Create a task to execute code Task.Run or TaskFactory.StartNew (not the Task constructor or Task.Start)
Create a task wrapper for an operation or event TaskFactory.FromAsync or TaskCompletionSource<T>
Support cancellation CancellationTokenSource and CancellationToken
Report progress IProgress<T> and Progress<T>
Handle streams of data TPL Dataflow or Reactive Extensions
Synchronize access to a shared resource SemaphoreSlim
Asynchronously initialize a resource AsyncLazy<T>
Async-ready producer/consumer structures TPL Dataflow or AsyncCollection<T>

Async and Await Guidelines

There are many new await-friendly techniques that should be used instead of the old blocking techniques. If you have any of these Old examples in your new async code, you’re Doing It Wrong(TM):

Old New Description
task.Wait await task Wait/await for a task to complete
task.Result await task Get the result of a completed task
Task.WaitAny await Task.WhenAny Wait/await for one of a collection of tasks to complete
Task.WaitAll await Task.WhenAll Wait/await for every one of a collection of tasks to complete
Thread.Sleep await Task.Delay Wait/await for a period of time
Taskconstructor Task.Run or TaskFactory.StartNew Create a code-based task

 

C# Yield Usage

You can use a yield return statement to return each element one at a time. It removes the need for an explicit extra class.
You can use a yield break statement to end the iteration.

public class PowersOf2
{
    static void Main()
    {
        // Display powers of 2 up to the exponent of 8:
        foreach (int i in Power(2, 8))
        {
            Console.Write("{0} ", i);
        }
    }

    public static System.Collections.Generic.IEnumerable<int> Power(int number, int exponent)
    {
        int result = 1;

        for (int i = 0; i < exponent; i++)
        {
            result = result * number;
            yield return result;
        }
    }

    // Output: 2 4 8 16 32 64 128 256
}
public static class GalaxyClass
{
    public static void ShowGalaxies()
    {
        var theGalaxies = new Galaxies();
        foreach (Galaxy theGalaxy in theGalaxies.NextGalaxy)
        {
            Debug.WriteLine(theGalaxy.Name + " " + theGalaxy.MegaLightYears.ToString());
        }
    }

    public class Galaxies
    {
        public System.Collections.Generic.IEnumerable<Galaxy> NextGalaxy
        {
            get
            {
                yield return new Galaxy { Name = "Tadpole", MegaLightYears = 400 };
                yield return new Galaxy { Name = "Pinwheel", MegaLightYears = 25 };
                yield return new Galaxy { Name = "Milky Way", MegaLightYears = 0 };
                yield return new Galaxy { Name = "Andromeda", MegaLightYears = 3 };
            }
        }
    }

    public class Galaxy
    {
        public String Name { get; set; }
        public int MegaLightYears { get; set; }
    }
}

Getting And Setting Property Attribute Value By Property Name

You can change property attribute value of an existing attribute at runtime. Below I created a helper class for getting and setting BrowsableAttribute value.

    public static class AttributeHelper
    {
        public static bool GetBrowsable(Type type, string propertyName)
        {
            PropertyDescriptor pd = TypeDescriptor.GetProperties(type)[propertyName];

            if (pd == null)
            {
                return false;
            }
            else
            {
                BrowsableAttribute ba = (BrowsableAttribute)pd.Attributes[typeof(BrowsableAttribute)];
                return (bool)ba.GetType().GetField("browsable", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(ba);
            }
        }

        public static void SetBrowsable(Type type, string propertyName, bool browsable)
        {
            PropertyDescriptor pd = TypeDescriptor.GetProperties(type)[propertyName];

            if (pd != null)
            {
                BrowsableAttribute ba = (BrowsableAttribute)pd.Attributes[typeof(BrowsableAttribute)];
                ba.GetType().GetField("browsable", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(ba, browsable);
            }
        }
    }

Dinamik Olarak ResourceDictionary, Model, ViewModel ve View Ekleme

Çalışma zamanında WPF uygulamalarımızda xaml dosyalarını XamlReader.Load fonksiyonu ile yükleyebiliyoruz. Bunun dışında cs dosyalarını yükleyip derleyebiliyoruz. Bu sayede sadece klasörlerdeki dosyaları değiştirerek aynı uygulamayı farklı şekillerde çalıştırabiliyoruz. Örnek bir proje oluşturdum. Bu projede ResourceDictionary, Model, ViewModel ve View dosyaları uygulama çalışırken ilgili klasörlerden yüklenmektedir. Projeye buradan ulaşabilirsiniz.

WPF ile Çok İşlemli Uygulama (Multi-Process Application)

Çok-işlemli uygulamalar aynı anda birden fazla uygulama arasında iletişim kurarak tek uygulama gibi çalışırlar. Uygulamaların direkt olarak birbirine bağlı olmaması sayesinde yüksek güvenlik, ana uygulama çalışırken diğer uygulamaları güncelleme, uygulamalardan biri çökse bile ana uygulamanın çökmemesi gibi bazı avantajları vardır.

WPF ile MPA mimarisinde çalışan bir uygulama geliştirdim. Uygulamalar arasındaki iletişimi Zyan Communication Framework ile IPC kullanarak sağladım. Buradan oluşturduğum projeyi indirebilirsiniz.

BLAKE2 ile Hash Oluşturma

BLAKE2, MD5, SHA-1, SHA-2 ve SHA-3’ten daha hızlı bir kriptografik hash algoritması olmakla birlikte, en son standart olan SHA-3 kadar güvenlidir. BLAKE2, yüksek hız, güvenlik ve basitlik nedeniyle birçok projeyle kabul edilmiştir.

BLAKE2’nin iki çeşit algoritması vardır:

  • BLAKE2b (veya sadece BLAKE2) 64 bitlik platformlar (NEON etkinleştirilmiş ARM’lar da dahil olmak üzere) için optimize edilmiştir ve 1 ile 64 bayt arasında herhangi bir boyutta özet üretir.
  • BLAKE2s 8 ila 32 bitlik platformlar için optimize edilmiştir ve 1 ila 32 bayt arasında herhangi bir boyutta özet üretir.

Daha fazla bilgi için https://blake2.net/ adresini ziyaret edebilirsiniz.

Github’daki C# kütüphanesini kullanarak, BLAKE2b ile minimum 8 bit, maksimum 512 bit boyutunda hash üretebiliyoruz. Aşağıdaki örnekte boyut 512 bit verildi. Blake2Sharp kütüphanesini https://github.com/BLAKE2/BLAKE2 adresinden indirebilirsiniz.

private static string Blake2Hash(string text)
{
    return BitConverter.ToString(Blake2B.ComputeHash(Encoding.UTF8.GetBytes(text), 
                                 new Blake2BConfig { OutputSizeInBits = 512 })).Replace("-", "");
}

Attached Dependency Property ile Resimli Tuş Oluşturma

Yeni bir kontrol oluşturmadan, mevcut Button kontrolüne ImageSource ve Text ekleyerek resimli tuş oluşturabiliyoruz. Ek olarak resim yüklenirken durumunu tuşun üzerinde görüntülemek için ShowProgress ve ProgressValue ekledim.
Buradan örnek projeyi indirebilirsiniz.

<Window x:Class="LazyImageButton.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ap="clr-namespace:LazyImageButton.AttachedProperties"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Button Style="{StaticResource SolidImageButtonStyle}" ap:ImageButton.ImageSource="{Binding Source}" ap:ImageButton.Text="Text" ap:ImageButton.ShowProgress="True" ap:ImageButton.ProgressValue="{Binding ProgressValue}"/>
    </Grid>
</Window>

REST Servis Çağrısı

REST GET/POST/PUT/DELETE yapmak için uyguladığım adımlar şu şekilde:

  • Visual Studio’da Visual C#/Windows Desktop/Console Application projesi oluşturdum. Adını RestClientSample koydum.
  • NuGet üzerinden Microsoft.AspNet.WebApi.Client paketini yükledim.
  • Rest servise gönderilecek ve servisten dönecek nesnelere karşılık gelen sınıfları oluşturdum.
  • Program.cs’yi aşağıdaki gibi değiştirdim.
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace RestClientSample
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                RunAsync().Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
            }

            Console.ReadKey();
        }

        static async Task RunAsync()
        {
            using (var client = new HttpClient())
            {
                // Servis adresi
                client.BaseAddress = new Uri("http://webadresi.com/");
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                // Servis Basic Http Authentication istiyorsa eklenir.
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(new UTF8Encoding().GetBytes(string.Format("{0}:{1}", "TestUser", "Pass"))));

                // HTTP GET
                HttpResponseMessage response = await client.GetAsync("api/IotDatas/1");
                if (response.IsSuccessStatusCode)
                {
                    IotData product = await response.Content.ReadAsAsync<IotData>();
                    Console.WriteLine("{0}\t${1}\t{2}", product.Name, product.Value, product.CreateDate);
                }

                // HTTP POST
                var iotData = new IotData { Name = "Test", Value = null, CreateDate = DateTime.Now };
                response = await client.PostAsJsonAsync("api/IotDatas", iotData);
                if (response.IsSuccessStatusCode)
                {
                    Uri gizmoUrl = response.Headers.Location;

                    // HTTP PUT
                    iotData.Value = null;
                    response = await client.PutAsJsonAsync(gizmoUrl, iotData);

                    // HTTP DELETE
                    response = await client.DeleteAsync(gizmoUrl);
                }
            }
        }
    }
}

DevExpress MVVM Kütüphanesi ile POCO View Model Oluşturma

Model-View-ViewModel deseni WPF ve Silverlight uygulamaları yazmak için kullanılan en popüler desendir. İş uygulamalarında oldukça yararlı olan birim testleri için esnek bir mimari geliştirmeye olanak sağlar.

Geleneksel MVVM geliştirmede bağlanabilir nesneler ve komutlar için ciddi oranda View Model kodu yazılır. DevExpress MVVM kütüphanesi bu işi oldukça kolaylaştırmış durumda. Bu kütüphane sayesinde POCO sınıflarını kullanarak çok hızlı bir şekilde View Model oluşturabiliyoruz. Ücretsiz olmasına karşın oldukça zengin bir kütüphane.

Projeye GitHub üzerinden ulaşabiliyoruz. Examples klasörü altında bir çok örnek kod da mevcut.
https://github.com/DevExpress/DevExpress.Mvvm.Free

Örnek kullanımını göstermek için bir Visual Studio’da bir örnek WPF projesi oluşturdum ve aşağıdaki adımları uyguladım.

  • Düzenli olması için bu projeye Views, ViewModels ve Models klasörlerini ekledim.
    MainWindow’u Views klasörüne taşıdım.
  • MainWindow.xaml’de MainWindow.cs’de namespace’leri düzenledim.
  • App.xaml’de StartupUri’yi “Views/MainWindow” olarak değiştirdim.
  • NuGet ile “DevExpressMvvm” kütüphanesini ekledim.
  • Models altına “Customer” sınıfını ekledim.
  • ViewModels altına “MainWindowViewModel” sınıfını ekledim.
  • MainWindow.xaml’e aşağıdaki namespace’leri ekledim.
    xmlns:ViewModels="clr-namespace:DxMvvmSample.ViewModels"
    xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
    
  • MainWindow.xaml’de Window’a ViewModel’i aşağıdaki gibi bağladım.
    DataContext="{dxmvvm:ViewModelSource Type=ViewModels:MainWindowViewModel}"
    
  • View Model üzerinden MessageBox gösterebilmek için MainWindow.xaml’e aşağıdaki kodu ekledim.
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:MessageBoxService/>
    </dxmvvm:Interaction.Behaviors>
    
  • Ekranda basit bir giriş formu oluşturdum. Binding’leri ekledim.
  • PasswordBox’un Password özelliği “Dependency Property” olmadığı için orada “DependencyPropertyBehavior” kullandım.

Sonuç olarak aşağıdaki gibi Model, View ve View Model sınıfları oluştu.

Model:

namespace DxMvvmSample.Models
{
    public class Customer
    {
        public virtual string UserName { get; set; }
    }
}

View:

<Window x:Class="DxMvvmSample.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ViewModels="clr-namespace:DxMvvmSample.ViewModels" xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:DxMvvmSample" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525" WindowStartupLocation="CenterScreen" DataContext="{dxmvvm:ViewModelSource Type=ViewModels:MainWindowViewModel}">
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:MessageBoxService/>
    </dxmvvm:Interaction.Behaviors>
    <Grid>
        <Grid Width="200" VerticalAlignment="Center" Background="AliceBlue">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="48"/>
                <RowDefinition Height="48"/>
                <RowDefinition Height="48"/>
            </Grid.RowDefinitions>
            <TextBlock Text="User Name:" VerticalAlignment="Center" Margin="3,0,3,0"/>
            <TextBox Grid.Column="1" Text="{Binding Customer.UserName}" VerticalContentAlignment="Center"/>
            <TextBlock Grid.Row="1" Text="Password:" VerticalAlignment="Center" Margin="3,0,3,0"/>
            <PasswordBox Grid.Column="1" Grid.Row="1" VerticalContentAlignment="Center">
                <dxmvvm:Interaction.Behaviors>
                    <dxmvvm:DependencyPropertyBehavior PropertyName="Password" EventName="PasswordChanged" Binding="{Binding Password, Mode=TwoWay}"/>
                </dxmvvm:Interaction.Behaviors>
            </PasswordBox>
            <Button Grid.ColumnSpan="2" Grid.Row="2" Content="Login" Command="{Binding LoginCommand}"/>
        </Grid>
    </Grid>
</Window>

View Model:

using DevExpress.Mvvm;
using DevExpress.Mvvm.POCO;
using DxMvvmSample.Models;

namespace DxMvvmSample.ViewModels
{
    public class MainWindowViewModel
    {
        protected MainWindowViewModel()
        {
            Customer = ViewModelSource.Create(() => new Customer());
        }

        public static MainWindowViewModel Create()
        {
            return ViewModelSource.Create(() => new MainWindowViewModel());
        }

        protected virtual IMessageBoxService MessageBoxService { get { return null; } }

        public virtual string Password { get; set; }

        public virtual Customer Customer { get; set; }

        public void Login()
        {
            if (CanLogin())
            {
                MessageBoxService.ShowMessage("Welcome " + Customer.UserName + "!");
            }
            else
            {
                MessageBoxService.ShowMessage("Failed.");
            }
        }

        public bool CanLogin()
        {
            return !string.IsNullOrWhiteSpace(Customer.UserName) && !string.IsNullOrEmpty(Password);
        }
    }
}

Buradan oluşturduğum örnek projeyi indirebilirsiniz.