C# Functional Programming In-Depth (14) Asynchronous Function

[LINQ via C# series]

[C# functional programming in-depth series]

Asynchronous function can improve the responsiveness and scalability of the application and service. C# 5.0 introduces asynchronous function to greatly simplify the async programming model.

Task, Task<TResult> and asynchrony

The C# async programming model uses System.Threading.Tasks.Task to represent async operation without output, and uses System.Threading.Tasks.Task<TResult> to represent async operation with TResult output:

namespace System.Threading.Tasks

{

    public partial class Task : IAsyncResult

    {

        public Task(Action action); // Wraps () –> void function.

        public void Start();

        public void Wait();

        public TaskStatus Status { get; } // Created, WaitingForActivation, WaitingToRun, Running, WaitingForChildrenToComplete, RanToCompletion, Canceled, Faulted.

 

        public bool IsCanceled { get; }

        public bool IsCompleted { get; }

        public bool IsFaulted { get; }

        public AggregateException Exception { get; }

        Task ContinueWith(Action<Task> continuationAction);

        Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction);

        // Other members.

    }

    public partial class Task<TResult> : Task

    {

        public Task(Func<TResult> function); // Wraps () –> TResult function.

        public TResult Result { get; }

        public Task ContinueWith(Action<Task<TResult>> continuationAction);

        public Task<TNewResult> ContinueWith<TNewResult>(

            Func<Task<TResult>, TNewResult> continuationFunction);

        // Other members.

    }

}

Task can be constructed with () –>void function, and Task<TResult> can be constructed with () –> TResult function. They can be started by calling the Start method. A () –> void function or () –> TResult function runs synchronously on the thread. In contrast, a task runs asynchronously, and does not block the current thread. Its status can be queried by the Status, IsCanceled, IsCompleted, IsFaulted properties. A task can be waited by calling its Wait method, which blocks the current thread until the task is completed successfully, or fails, or is cancelled. For Task<TResult>, when the underlying async operation is completed successfully, the result is available through Result property. For Task or Task<TResult>, if the underlying async operation fails with exception, the exception is available through the Exception property. A task can be chained with another async continuation operation by calling the ContinueWith methods. When the task finishes running, the specified continuation starts running asynchronously. If the task already finishes running when its ContinueWith method is called, then the specified continuation immediately starts running. The following example constructs and starts a task to read a file, and chains another continuation task to write the contents to another file:

internal static partial class Functions

{

    internal static void ConstructTask(string readPath, string writePath)

    {

        Thread.CurrentThread.ManagedThreadId.WriteLine(); // 10

        Task<string> task = new Task<string>(() =>

        {

            Thread.CurrentThread.ManagedThreadId.WriteLine(); // 8

            return File.ReadAllText(readPath);

        });

        task.Start();

        Task continuationTask = task.ContinueWith(antecedentTask =>

        {

            Thread.CurrentThread.ManagedThreadId.WriteLine(); // 9

            object.ReferenceEquals(antecedentTask, task).WriteLine(); // True

            if (antecedentTask.IsFaulted)

            {

                antecedentTask.Exception.WriteLine();

            }

            else

            {

                File.WriteAllText(writePath, antecedentTask.Result);

            }

        });

        continuationTask.Wait();

    }

}

As async operations, when tasks are started, the wrapped functions are by default scheduled to runtime’s thread pool to execute, so that their thread ids are different from the caller thread id.

In .NET Framework, Task also implements System.Threading.IThreadPoolWorkItem and IDisposable interfaces. Its usage is the same as in .NET Core. Do not bother disposing tasks.

Task also provides Run methods to construct and automatically start tasks:

namespace System.Threading.Tasks

{

    public partial class Task : IAsyncResult

    {

        public static Task Run(Action action);

        public static Task<TResult> Run<TResult>(Func<TResult> function);

    }

}

Now compare the following functions:

internal static void Write(string path, string contents) => File.WriteAllText(path, contents);

internal static string Read(string path) => File.ReadAllText(path);

internal static Task WriteAsync(string path, string contents) =>

    Task.Run(() => File.WriteAllText(path, contents));

internal static Task<string>ReadAsync(string path) => Task.Run(() => File.ReadAllText(path));

Write without output and Read with string output run synchronously. WriteAsync with Task output and ReadAsync with Task<string> output run asynchronously, where Task can be viewed as future void, and Task<TResult> can be viewed as future TResult result. Here WriteAsync and ReadAsync become async by simply offloading the operations to thread pool. This is for demonstration purpose, and does not bring any scalability improvement. A better implementation is discussed later.

internal static void CallReadWrite(string path, string contents)

{

    Write(path, contents); // Blocking.

    // When the underlying write operation is done, the call is completed with no result.

    string result = Read(path); // Blocking.

    // When the underlying read operation is done, the call is completed with result available.

    Task writeTask = WriteAsync(path, contents); // Non-blocking.

    // When the task is constructed and started, the call is completed immediately.

    // The underlying write operation is scheduled, and will be completed in the future with no result.

   Task<string> readTask = ReadAsync(path); // Non-blocking.

    // When the task is constructed and started, the call is completed immediately.

    // The underlying read operation is scheduled, and will be completed in the future with result available.

}

When Write is called, its execution blocks the current thread. When the writing operation is done synchronously, it does not output any result, and then the caller thread can continue execution. Similarly, when Read is called, its execution blocks the current thread too. When the reading operation is done synchronously, it outputs the result, so that the result is available to the caller and the caller can continue execution. When WriteAsync is called, it calls Task.Run to construct a Task instance with the writing operation, start the task, then immediately outputs the task to the caller. Then the caller can continue without being blocked by the writing operation execution. By default, the writing operation is scheduled to thread pool, when it is done, the writing operation outputs no result, and the task’s Status is updated. Similarly, when ReadAsync is called, it also calls Task.Run to construct a Task<string> instance with the reading operation, start the task, then immediately outputs the task to the caller. Then the caller can continue without being blocked by the reading operation execution. By default, the reading operation is also scheduled to thread pool, when it is done, the reading operation has a result, and the task’s Status is updated, with the result available through the Result property.

Named async function

By default, named async function’s output type is Task or Task<TResult>, and has an Async or AsyncTask postfix in the name as the convention. The following example is a file read and write workflow of sync function calls:

internal static void ReadWrite(string readPath, string writePath)

{

    string contents = Read(readPath);

   Write(writePath, contents);

}

The same logic can be implemented by calling the async version of functions:

internal static async Task ReadWriteAsync(string readPath, string writePath)

{

    string contents = await ReadAsync(readPath);

    await WriteAsync(writePath, contents);

}

Here await keyword is used for each async function call, and the code structure remains the same as the sync workflow. When await keyword is used in function body, the async modifier is required for that function’s signature. Regarding the workflow outputs no result, the async function outputs Task (future void). This ReadWriteAsync function calls async functions, itself is also async function, since it has the async modifier and Task output. When ReadWriteAsync is called, it works the same way as ReadAsync and WriteAsync. it does not block its caller, and immediately outputs a task to represent the scheduled read and write workflow.

So the await keyword can be viewed as virtually waiting for the task’s underlying async operation to finish. If the task fails, exception is thrown. If the task is completed successfully, the continuation right after the await expression is called back. If the task has a result, await expression can extract the result. Therefore, the async workflow keeps the same looking of sync workflow. There is no ContinueWith call needed to build the continuation. The following example is a more complex database query workflow of sync function calls, with an int value as the query result:

internal static int Query(DbConnection connection, StreamWriter logWriter) // Output int.

{

    try

    {

        connection.Open(); // Output void.

        using (DbCommand command = connection.CreateCommand())

        {

            command.CommandText = "SELECT 1;";

            using (DbDataReader reader = command.ExecuteReader()) // Output DbDataReader.

            {

                if (reader.Read()) // Output bool.

                {

                    return (int)reader[0];

                }

                throw new InvalidOperationException("Failed to call sync functions.");

            }

        }

    }

    catch (SqlException exception)

    {

        logWriter.WriteLine(exception.ToString()); // Output void.

        throw new InvalidOperationException("Failed to call sync functions.", exception);

    }

}

Here the DbConnection.Open, DbCommand.ExecuteReader, DbDataReader.Read, StreamWriter.WriteLine functions have async version provided as DbConnection.OpenAsync, DbCommand.ExecuteReaderAsync, DbDataReader.ReadAsync, StreamWriter.WriteLineAsync. They output either Task or Task<TResult>. With the async and await keywords, it easy to call these async functions:

internal static async Task<int> QueryAsync(

    DbConnection connection, StreamWriter logWriter) // Output Task<int> instead of int.

{

    try

    {

        await connection.OpenAsync(); // Output Task instead of void.

        using (DbCommand command = connection.CreateCommand())

        {

            command.CommandText = "SELECT 1;";

            using (DbDataReader reader = await command.ExecuteReaderAsync()) // Output Task<DbDataReader> instead of DbDataReader.

            {

                if (await reader.ReadAsync()) // Output Task<bool> instead of bool.

                {

                    return (int)reader[0];

                }

                throw new InvalidOperationException("Failed to call async functions.");

            }

        }

    }

    catch (SqlException exception)

    {

        await logWriter.WriteLineAsync(exception.ToString()); // Output Task instead of void.

        throw new InvalidOperationException("Failed to call async functions.", exception);

    }

}

Again, the async workflow persists the same code structure as the sync workflow, including try-catch, using, if statements, etc. Without this syntax, it is a lot more complex to call ContinueWith and manually build above workflow. Regarding the async function has an int result, its output type is Task<int> (future int).

The above Write and Read functions calls File.WriteAllText and File.ReadAllText to execute sync I/O operation, which are internally implemented by calling StreamWriter.Write and StreamReader.ReadToEnd. Now with the async and await keywords, WriteAsync and ReadAsync can be reimplemented as real async I/O (assuming async I/O  is actually supported by the underlying operating system) by calling StreamWriter.WriteAsync and StreamReader.ReadToEndAsync:

internal static async Task WriteAsync(string path, string contents)

{

    // File.WriteAllText implementation:

    // using (StreamWriter writer = new StreamWriter(new FileStream(

    //   path: path, mode: FileMode.Create, access: FileAccess.Write,

    //   share: FileShare.Read, bufferSize: 4096, useAsync: false)))

    // {

    //    writer.Write(contents);

    // }

    using (StreamWriter writer = new StreamWriter(new FileStream(

        path: path, mode: FileMode.Create, access: FileAccess.Write,

        share: FileShare.Read, bufferSize: 4096, useAsync: true)))

    {

        await writer.WriteAsync(contents);

    }

}

internal static async Task<string>ReadAsync(string path)

{

    // File.ReadAllText implementation:

    // using (StreamReader reader = new StreamReader(new FileStream(

    //   path: path, mode: FileMode.Open, access: FileAccess.Read,

    //   share: FileShare.Read, bufferSize: 4096, useAsync: false)))

    // {

    //   return reader.ReadToEnd();

    // }

    using (StreamReader reader = new StreamReader(new FileStream(

        path: path, mode: FileMode.Open, access: FileAccess.Read,

        share: FileShare.Read, bufferSize: 4096, useAsync: true)))

    {

        return await reader.ReadToEndAsync();

    }

}

There is one special scenario where async function has void output – async function as event handler. For example, ObservableCollection<T> has a CollectionChanged event:

namespace System.Collections.ObjectModel

{

    public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged

    {

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        // Other members.

    }

}

namespace System.Collections.Specialized

{

    // (object, NotifyCollectionChangedEventArgs) –> void.

public delegate void NotifyCollectionChangedEventHandler(

        object sender, NotifyCollectionChangedEventArgs e);

}

This event requires its handler to be a function of type (object, NotifyCollectionChangedEventArgs) –> void. So, when defining an async function as the above event’s handler, that async function has void output instead of Task:

private static readonly StringBuilder Logs = new StringBuilder();

 

private static readonly StringWriter LogWriter = new StringWriter(logs);

 

// (object, NotifyCollectionChangedEventArgs) –> void.

private static async void CollectionChangedAsync(

    object sender, NotifyCollectionChangedEventArgs e) =>

    await LogWriter.WriteLineAsync(e.Action.ToString());

 

internal static void AddEventHandler()

{

    ObservableCollection<int> collection = new ObservableCollection<int>();

    collection.CollectionChanged += CollectionChangedAsync;

    collection.Add(1); // Fires CollectionChanged event.

}

The await keyword works with task from async function as well as any Task and Task<TResult> instance:

internal static async Task AwaitTasks(string path)

{

   Task<string> task1 = ReadAsync(path);

    string contents = await task1;

    // Equivalent to: string contents = await ReadAsync(path);

    Task task2 = WriteAsync(path, contents);

    await task2;

    // Equivalent to: await WriteAsync(path, contents);

    Task task3 = Task.Run(() => { });

    await task3;

    // Equivalent to: await Task.Run(() => { });

   Task<int> task4 = Task.Run(() => 0);

    int result = await task4;

    // Equivalent to: int result = await Task.Run(() => 0);

    Task task5 = Task.Delay(TimeSpan.FromSeconds(10));

    await task5;

    // Equivalent to: await Task.Delay(TimeSpan.FromSeconds(10));

   Task<int> task6 = Task.FromResult(result);

    result = await task6;

    // Equivalent to: result = await Task.FromResult(result);

}

If a task is never started, apparently it never finishes running. The code after its await expression is never called back:

internal static async Task HotColdTasks(string path)

{

    Task hotTask = new Task(() => { });

   hotTask.Start();

    await hotTask;

    hotTask.Status.WriteLine();

    Task coldTask = new Task(() => { });

    await coldTask;

   coldTask.Status.WriteLine(); // Never execute.

}

Task not started yet is called cold task, and task already started is called hot task. As a convention, any function with task output should always output a hot task. All APIs in .NET Standard follow this convention.

Awaitable-awaiter pattern

C# compiles the await expression with the awaitable-awaiter pattern. Besides Task and Task<TResult>, the await keyword can be used with any awaitable type. An awaitable type has a GetAwaiter instance or extension method to output an awaiter. An awaiter type implements System.Runtime.CompilerServices.INotifyCompletion interface, also has an IsCompleted property with bool output, and a GetResult instance method with or without output. The following IAwaitable and IAwaiter interfaces demonstrate the awaitable-awaiter pattern for operation with no result:

public interface IAwaitable

{

    IAwaiter GetAwaiter();

}

 

public interface IAwaiter : INotifyCompletion

{

    bool IsCompleted { get; }

 

    void GetResult(); // No result.

}

And the following IAwaitable<TResult> and IAwaiter<TResult> interfaces demonstrate the awaitable-awaiter pattern for operations with a result:

public interface IAwaitable<TResult>

{

    IAwaiter<TResult>GetAwaiter();

}

public interface IAwaiter<TResult>: INotifyCompletion

{

    bool IsCompleted { get; }

    TResult GetResult(); // TResult result.

}

And INotifyCompletion interface has a single OnCompleted method to chain a continuation:

namespace System.Runtime.CompilerServices

{

    public interface INotifyCompletion

    {

        void OnCompleted(Action continuation);

    }

}

Here is how Task and Task<TResult> implement the awaitable-awaiter pattern. Task can be virtually viewed as implementation of IAwaitable, it has a GetAwaiter instance method outputting System.Runtime.CompilerServices.TaskAwaiter, which can be virtually viewed as implementation of IAwaiter; Similarly, Task<TResult> can be virtually viewed as implementation of IAwaitable<TResult>, it has a GetAwaiter method outputting System.Runtime.CompilerServices.TaskAwaiter<TResult>, which can be virtually viewed as implementation of IAwaiter<TResult>:

namespace System.Threading.Tasks

{

    public partial class Task : IAsyncResult

    {

        public TaskAwaiter GetAwaiter();

    }

    public partial class Task<TResult> : Task

    {

        public TaskAwaiter<TResult> GetAwaiter();

    }

}

namespace System.Runtime.CompilerServices

{

    public struct TaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion

    {

        public bool IsCompleted { get; }

        public void GetResult(); // No result.

        public void OnCompleted(Action continuation);

        // Other members.

    }

    public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion

    {

        public bool IsCompleted { get; }

        public TResult GetResult(); // TResult result.

        public void OnCompleted(Action continuation);

        // Other members.

    }

}

Any other type can be used with the await keyword, as long as the awaitable-awaiter pattern is implemented. Take delegate type Action as example, a GetAwaiter method can be easily implemented as its extension method, by reusing above TaskAwaiter:

public static TaskAwaiter GetAwaiter(this Action action) => Task.Run(action).GetAwaiter();

Similarly, this pattern can be implemented for Func<TResult>, by reusing TaskAwaiter<TResult>:

public static TaskAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function) =>

    Task.Run(function).GetAwaiter();

Now the await keyword can be directly used with a function of Action type or Func<TResult> type:

internal static async Task AwaitFunctions(string readPath, string writePath)

{

    Func<string>read = () => File.ReadAllText(readPath);

    string contents = await read;

    Action write = () => File.WriteAllText(writePath, contents);

    await write;

}

Async state machine

As fore mentioned, with async and await keywords, an async function outputs a task immediately, so it is non-blocking. At compile time, the workflow of an async function is compiled to an async state machine. At runtime, when this async function is called, it just starts that generated async state machine , and immediately outputs a task representing the workflow in the async state machine. To demonstrate this, define the following async methods:

internal static async Task<T> Async<T>(T value)

{

    T value1 = Start(value);

    T result1 = await Async1(value1);

    T value2 = Continuation1(result1);

    T result2 = await Async2(value2);

    T value3 = Continuation2(result2);

    T result3 = await Async3(value3);

    T result = Continuation3(result3);

    return result;

}

internal static T Start<T>(T value) => value;

internal static Task<T> Async1<T>(T value) => Task.Run(() => value);

internal static T Continuation1<T>(T value) => value;

internal static Task<T> Async2<T>(T value) => Task.FromResult(value);

internal static T Continuation2<T>(T value) => value;

internal static Task<T> Async3<T>(T value) => Task.Run(() => value);

internal static T Continuation3<T>(T value) => value;

After compilation, the async modifier is gone. The async function becomes a normal function to start an async state machine:

[AsyncStateMachine(typeof(AsyncStateMachine<>))]

internal static Task<T> CompiledAsync<T>(T value)

{

    AsyncStateMachine<T>asyncStateMachine = new AsyncStateMachine<T>()

    {

        Value = value,

        Builder = AsyncTaskMethodBuilder<T>.Create(),

        State = -1 // -1 means start.

    };

    asyncStateMachine.Builder.Start(ref asyncStateMachine);

    return asyncStateMachine.Builder.Task;

}

And the generated async state machine is a structure in release build, and a class in debug build:

[CompilerGenerated]

[StructLayout(LayoutKind.Auto)]

private struct AsyncStateMachine<TResult>: IAsyncStateMachine

{

    public int State;

    public AsyncTaskMethodBuilder<TResult> Builder;

    public TResult Value;

    private TaskAwaiter<TResult> awaiter;

    void IAsyncStateMachine.MoveNext()

    {

        TResult result;

        try

        {

            switch (this.State)

            {

                case -1: // Start code from the beginning to the 1st await.

                    // Workflow begins.

                    TResult value1 = Start(this.Value);

                    this.awaiter = Async1(value1).GetAwaiter();

                    if (this.awaiter.IsCompleted)

                    {

                        // If the task returned by Async1 is already completed, immediately execute the continuation.

                        goto case 0;

                    }

                    else

                    {

                        this.State = 0;

                        // If the task returned by Async1 is not completed, specify the continuation as its callback.

                        this.Builder.AwaitUnsafeOnCompleted(ref this.awaiter, ref this);

                        // Later when the task returned by Async1 is completed, it calls back MoveNext, where State is 0.

                        return;

                    }

                case 0: // Continuation code from after the 1st await to the 2nd await.

                    // The task returned by Async1 is completed. The result is available immediately through GetResult.

                    TResult result1 = this.awaiter.GetResult();

                    TResult value2 = Continuation1(result1);

                    this.awaiter = Async2(value2).GetAwaiter();

                    if (this.awaiter.IsCompleted)

                    {

                        // If the task returned by Async2 is already completed, immediately execute the continuation.

                        goto case 1;

                    }

                    else

                    {

                        this.State = 1;

                        // If the task returned by Async2 is not completed, specify the continuation as its callback.

                        this.Builder.AwaitUnsafeOnCompleted(ref this.awaiter, ref this);

                        // Later when the task returned by Async2 is completed, it calls back MoveNext, where State is 1.

                        return;

                    }

                case 1: // Continuation code from after the 2nd await to the 3rd await.

                    // The task returned by Async2 is completed. The result is available immediately through GetResult.

                    TResult result2 = this.awaiter.GetResult();

                    TResult value3 = Continuation2(result2);

                    this.awaiter = Async3(value3).GetAwaiter();

                    if (this.awaiter.IsCompleted)

                    {

                        // If the task returned by Async3 is already completed, immediately execute the continuation.

                        goto case 2;

                    }

                    else

                    {

                        this.State = 2;

                        // If the task returned by Async3 is not completed, specify the continuation as its callback.

                        this.Builder.AwaitUnsafeOnCompleted(ref this.awaiter, ref this);

                        // Later when the task returned by Async3 is completed, it calls back MoveNext, where State is 1.

                        return;

                    }

                case 2: // Continuation code from after the 3rd await to the end.

                    // The task returned by Async3 is completed. The result is available immediately through GetResult.

                    TResult result3 = this.awaiter.GetResult();

                    result = Continuation3(result3);

                    this.State = -2; // -2 means end.

                    this.Builder.SetResult(result);

                    // Workflow ends.

                    return;

            }

        }

        catch (Exception exception)

        {

            this.State = -2; // -2 means end.

            this.Builder.SetException(exception);

        }

    }

    [DebuggerHidden]

    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine asyncStateMachine) =>

        this.Builder.SetStateMachine(asyncStateMachine);

}

The generated async state machine is a finite state machine:

clip_image002

The workflow is compiled into a switch statement in its MoveNext method. The original workflow is split to 4 parts by the 3 await keywords, and compiled to 4 cases of the switch statement. The parameter of the workflow is compiled as a field of the state machine, so it can be accessed by the workflow inside MoveNext. When the state machine is initialized, its initial state is –1, which means to start. Once the state machine is started, MoveNext is called, and the case –1 block is executed, which has the code from the beginning of the workflow to the first await expression, which is compiled to a GetAwaiter call. If the awaiter is already completed, then the continuation should immediate be executed by going to case 0 block directly; If the awaiter is not completed, the continuation (MoveNext call with next state 0) is specified as the awaiter’s callback when it is completed in the future. In either case, when code in case 0 block is executed, the previous awaiter is already completed, and its result is immediately available through its GetResult method. The execution goes on in the same pattern, until the last block of case 2 is executed.

Runtime context capture

At runtime, when executing an await expression, if the awaited task is not completed yet, the continuation is scheduled as callback. As a result, the continuation can be executed by a thread different from initial caller thread. By default, the initial thread’s runtime context information is captured, and are reused by the callback thread to execute the continuation. To demonstrate this, the above awaitable-awaiter pattern for Action can be re-implemented with the following custom awaiter:

public static IAwaiter GetAwaiter(this Action action) =>

    new ActionAwaiter(Task.Run(action));

 

public class ActionAwaiter : IAwaiter

{

    private readonly (SynchronizationContext, TaskScheduler, ExecutionContext) runtimeContext;

 

    private readonly Task task;

 

public ActionAwaiter(Task task) =>

        (this.task, this.runtimeContext) = (task, RuntimeContext.Capture()); // Capture runtime context when initialized.

 

    public bool IsCompleted => this.task.IsCompleted;

 

    public void GetResult() => this.task.Wait();

 

    public void OnCompleted(Action continuation) => this.task.ContinueWith(task =>

        this.runtimeContext.Execute(continuation));// Execute continuation on captured runtime context.

}

When the awaiter is constructed, it captures the runtime context information, including System.Threading.SynchronizationContext, System.Threading.Tasks.TaskScheduler, and System.Threading.ExecutionContext of current thread. Then in OnCompleted, when the continuation is called back, it is executed with the previously captured runtime context information. The custom awaiter can be implemented for Func<TResult> in the same pattern:

public static IAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function) =>

    new FuncAwaiter<TResult>(Task.Run(function));

 

public class FuncAwaiter<TResult> : IAwaiter<TResult>

{

    private readonly (SynchronizationContext, TaskScheduler, ExecutionContext) runtimeContext =

        RuntimeContext.Capture();

 

    private readonly Task<TResult> task;

 

    public FuncAwaiter(Task<TResult> task) => (this.task, this.runtimeContext) = (task, RuntimeContext.Capture()); // Capture runtime context when initialized.

 

    public bool IsCompleted => this.task.IsCompleted;

 

    public TResult GetResult() => this.task.Result;

 

    public void OnCompleted(Action continuation) => this.task.ContinueWith(task =>

        this.runtimeContext.Execute(continuation)); // Execute continuation on captured runtime context.

}

The following is a basic implementation of runtime context capture and resume:

public static class RuntimeContext

{

    public static (SynchronizationContext, TaskScheduler, ExecutionContext) Capture() =>

        (SynchronizationContext.Current, TaskScheduler.Current, ExecutionContext.Capture());

 

    public static void Execute(

        this (SynchronizationContext, TaskScheduler, ExecutionContext) runtimeContext, Action continuation)

    {

        var (synchronizationContext, taskScheduler, executionContext) = runtimeContext;

        if (synchronizationContext != null && synchronizationContext.GetType() != typeof(SynchronizationContext))

        {

            if (synchronizationContext == SynchronizationContext.Current)

            {

                executionContext.Run(continuation);

            }

            else

            {

                executionContext.Run(() => synchronizationContext.Post(

                    d: state => continuation(), state: null));

            }

            return;

        }

        if (taskScheduler != null && taskScheduler != TaskScheduler.Default)

        {

            Task continuationTask = new Task(continuation);

            continuationTask.Start(taskScheduler);

            return;

        }

        executionContext.Run(continuation);

    }

 

    private static void Run(this ExecutionContext executionContext, Action continuation)

    {

        if (executionContext != null)

        {

            ExecutionContext.Run(

                executionContext: executionContext, callback: state => continuation(), state: null);

        }

        else

        {

            continuation();

        }

    }

}

When Capture is called, it captures a 3-tuple of SynchronizationContext, TaskScheduler, and ExecutionContext When the continuation is executed, first the previously captured SynchronizationContext is checked. If a specialized SynchronizationContext is captured and it is different from current SynchronizationContext, then the continuation is executed with the captured SynchronizationContext and ExecutionContext. When there is no specialized SynchronizationContext captured, then the TaskScheduler is checked. If a specialized TaskScheduler is captured, it is used to schedule the continuation as a task. For all the other cases, the continuation is executed with the captured ExecutionContext.

Task and Task<TResult> provides a ConfigureAwait method to specify whether the continuation is marshalled to the previously captured runtime context:

namespace System.Threading.Tasks

{

    public partial class Task : IAsyncResult

    {

        public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext);

    }

    public partial class Task<TResult> : Task

    {

        public ConfiguredTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext);

    }

}

To demonstrate the runtime context capture, define a custom task scheduler, which simply start a background thread to execute each task:

public class BackgroundThreadTaskScheduler : TaskScheduler

{

protected override IEnumerable<Task> GetScheduledTasks() =>

        throw new NotImplementedException();

 

    protected override void QueueTask(Task task) =>

        new Thread(() => this.TryExecuteTask(task)) { IsBackground = true }.Start();

 

protected override bool TryExecuteTaskInline(

        Task task, bool taskWasPreviouslyQueued) =>

        this.TryExecuteTask(task);

}

The following async function has 2 await expressions, where ConfigureAwait is called with different bool values:

internal static async Task ConfigureRuntimeContextCapture(

    string readPath, string writePath)

{

    TaskScheduler taskScheduler1 = TaskScheduler.Current;

string contents = await ReadAsync(readPath).ConfigureAwait(

        continueOnCapturedContext: true);

    // Equivalent to: await ReadAsync(readPath);

 

    // Continuation is executed with captured runtime context.

    TaskScheduler taskScheduler2 = TaskScheduler.Current;

    object.ReferenceEquals(taskScheduler1, taskScheduler2).WriteLine(); // True

await WriteAsync(writePath, contents).ConfigureAwait(

        continueOnCapturedContext: false);

    // Continuation is executed without captured runtime context.

    TaskScheduler taskScheduler3 = TaskScheduler.Current;

    object.ReferenceEquals(taskScheduler1, taskScheduler3).WriteLine(); // False

}

To demonstrate the task scheduler capture, call the above async function by specifying the custom task scheduler:

internal static async Task CallConfigureContextCapture(string readPath, string writePath)

{

Task<Task> task = new Task<Task>(() =>

        ConfigureRuntimeContextCapture(readPath, writePath));

    task.Start(new BackgroundThreadTaskScheduler());

    await task.Unwrap(); // Equivalent to: await await task;

}

Here since async function ConfigureRuntimeContextCapture outputs Task, so the task constructed with () -> Task function is of type Task<Task>. Similarly, if task is constructed with () -> Task<TResult> function, the constructed task is of type Task<Task<TResult>>. For this scenario, Unwrap extension methods are provided to convert nested task to normal task:

namespace System.Threading.Tasks

{

    public static class TaskExtensions

    {

        public static Task Unwrap(this Task<Task> task);

        public static Task<TResult> Unwrap<TResult>(this Task<Task<TResult>> task);

    }

}

When start executing ConfigureRuntimeContextCapture, the initial task scheduler is specified to be the custom task scheduler, BackgroundThreadTaskScheduler. In the first await expression, ConfigureAwait is called with true, so that the runtime context information is captured and the continuation is executed with the captured runtime context information. This is the default behaviour, so calling ConfigureAwait with true is equivalent to not calling ConfigureAwait at all. As a result, the first continuation is executed with the same custom task scheduler. In the second await expression, ConfigureAwait is called with false, so the runtime context information is not captured. As a result, the second continuation is executed with the default task scheduler, System.Threading.Tasks.ThreadPoolTaskScheduler.

The runtime context capture can be also demonstrated by SynchronizationContext. SynchronizationContext is inherited by different implementations in different application models, for example:

·        ASP.NET: System.Web.AspNetSynchronizationContext

·        WPF: System.Windows.Threading.DispatcherSynchronizationContext

·        WinForms: System.Windows.Forms.WindowsFormsSynchronizationContext

·        Windows Universal: System.Threading.WinRTCoreDispatcherBasedSynchronizationContext

·        Xamarin.Android: Android.App.SyncContext

·        Xamarin.iOS: UIKit.UIKitSynchronizationContext

Take Windows Universal application as example. In Visual Studio on Windows, create a Windows Universal application, add a button to its UI:

<Button x:Name="Button" Content="Button" HorizontalAlignment="Center" VerticalAlignment="Center" Click="ButtonClick" />

In the code behind, implement the Click event handler as an async function:

private async void ButtonClick(object sender, RoutedEventArgs e)

{

    SynchronizationContext synchronizationContext1 = SynchronizationContext.Current;

    ExecutionContext executionContext1 = ExecutionContext.Capture();

await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(

        continueOnCapturedContext: true);

    // Equivalent to: await Task.Delay(TimeSpan.FromSeconds(1));

           

    // Continuation is executed with captured runtime context.

    SynchronizationContext synchronizationContext2 = SynchronizationContext.Current;

    Debug.WriteLine(synchronizationContext1 == synchronizationContext2); // True

    this.Button.Background = new SolidColorBrush(Colors.Blue); // UI update works.

await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(

        continueOnCapturedContext: false);

           

    // Continuation is executed without captured runtime context.

    SynchronizationContext synchronizationContext3 = SynchronizationContext.Current;

    Debug.WriteLine(synchronizationContext1 == synchronizationContext3); // False

    this.Button.Background = new SolidColorBrush(Colors.Yellow); // UI update fails.

    // Exception: The application called an interface that was marshalled for a different thread.

}

For Windows Universal application, the SynchronizationContext is only available for the UI thread, and application UI can only be updated with UI thread’s SynchronizationContext. When the button is clicked, the UI thread calls the async function ButtonClick, so the UI thread’s SynchronizationContext is captured. Similar to the previous example, when ConfigureAwait is called with true, the continuation is executed with the previously captured SynchronizationContext, so the continuation can update the UI successfully. When ConfigureAwait is called with true, the continuation is not executed with the SynchronizationContext captured from UI thread, and it fails to update the UI and throws exception.

Generalized async function output type and async method builder

Since C# 7, async function is supported to have any awaitable output type, as long as it has an async method builder specified. Take Func<TResult> is already awaitable with the previously defined GetAwaiter extension method as example, it is already awaitable with the previously defined GetAwaiter extension method, and can be used in await expression just like task. However, it cannot be used with async modifier to be async function output type just like task. To make Func<TResult> output type of async function, the following FuncAwaitable<TResult> type is defined as a wrapper of Func<TResult>. This wrapper is an awaitable type, its GetAwaiter instance method reuses previously defined FuncAwater<TResult> as its awaiter:

[AsyncMethodBuilder(typeof(AsyncFuncAwaitableMethodBuilder<>))]

public class FuncAwaitable<TResult>: IAwaitable<TResult>

{

    private readonly Func<TResult> function;

    public FuncAwaitable(Func<TResult> function) => this.function = function;

    public IAwaiter<TResult> GetAwaiter() =>

        new FuncAwaiter<TResult>(Task.Run(this.function));

}

This wrapper type is associated with its async method builder with System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. The async method builder for FuncAwaitable<TResult> is implemented as:

public class AsyncFuncAwaitableMethodBuilder<TResult>

{

    private AsyncTaskMethodBuilder<TResult> taskMethodBuilder;

    private TResult result;

    private bool hasResult;

    private bool useBuilder;

    public static AsyncFuncAwaitableMethodBuilder<TResult> Create() =>

        new AsyncFuncAwaitableMethodBuilder<TResult>()

        {

            taskMethodBuilder = AsyncTaskMethodBuilder<TResult>.Create()

        };

public void Start<TStateMachine>(ref TStateMachine stateMachine)

        where TStateMachine : IAsyncStateMachine =>

        this.taskMethodBuilder.Start(ref stateMachine);

    public void SetStateMachine(IAsyncStateMachine stateMachine) =>

        this.taskMethodBuilder.SetStateMachine(stateMachine);

    public void SetResult(TResult result)

    {

        if (this.useBuilder)

        {

            this.taskMethodBuilder.SetResult(result);

        }

        else

        {

            this.result = result;

            this.hasResult = true;

        }

    }

public void SetException(Exception exception) =>

        this.taskMethodBuilder.SetException(exception);

    public FuncAwaitable<TResult> Task

    {

        get

        {

            if (this.hasResult)

            {

                TResult result = this.result;

                return new FuncAwaitable<TResult>(() => result);

            }

            this.useBuilder = true;

            Task<TResult>task = this.taskMethodBuilder.Task;

            return new FuncAwaitable<TResult>(() => task.Result);

        }

    }

public void AwaitOnCompleted<TAwaiter, TStateMachine>(

        ref TAwaiter awaiter, ref TStateMachine stateMachine)

        where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine

    {

        this.useBuilder = true;

        this.taskMethodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);

    }

    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(

        ref TAwaiter awaiter, ref TStateMachine stateMachine)

        where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine

    {

        this.useBuilder = true;

        this.taskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);

    }

}

Now the FuncAwaitable<TResult> type can be output type of async function, just like task:

internal static async FuncAwaitable<T> AsyncFunctionWithFuncAwaitable<T>(T value)

{

    await Task.Delay(TimeSpan.FromSeconds(1));

    return value;

}

Its compilation is in the same pattern as async function with task output. The only difference is, in the generated async state machine, the builder field become the specified AsyncFuncAwaitableMethodBuilder<TResult>, instead of the AsyncTaskMethodBuilder<TResult> for task. And apparently, this async function can be called in the await expression since its output type FuncAwaitable<TResult> is awaitable:

internal static async Task CallAsyncFunctionWithFuncAwaitable<T>(T value)

{

    T result = await AsyncFunctionWithFuncAwaitable(value);

}

ValueTask<TResult> and performance

With the generalized async function output type support, Microsoft also provides a System.Threading.Tasks.ValueTask<TResult> awaitable structure in the System.Threading.Tasks.Extensions NuGet package:

namespace System.Threading.Tasks

{

    [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder<>))]

    [StructLayout(LayoutKind.Auto)]

    public struct ValueTask<TResult> : IEquatable<ValueTask<TResult>>

    {

        public ValueTask(TResult result);

        public ValueTask(Task<TResult> task);

        public ValueTaskAwaiter<TResult> GetAwaiter();

        // Other members.

    }

}

Its awaiter is System.Threading.Tasks.ValueTaskAwaiter<TResult>, and its async method builder is System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<TResult>, which are provided in the same NuGet package. So ValueTask<TResult> can be used with both await expression and async function. As a value type, it can be allocated and deallocated on stack, with better performance than reference type Task<TResult>. Also, unlike Task<TResult> as a wrapper of Func<TResult> operation, ValueTask<TResult> can be a wrapper of either Func<TResult> operation or TResult result. So ValueTask<TResult> can improve the performance for async function that may have result available before any async operation. The following example downloads data from the specified URI:

private static readonly Dictionary<string, byte[]> Cache =

    new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);

internal static async Task<byte[]> DownloadAsyncTask(string uri)

{

    // All code compiled to async state machine. When URI is cached, async state machine is still started.

    if (Cache.TryGetValue(uri, out byte[] cachedResult))

    {

        return cachedResult;

    }

    using (HttpClient httpClient = new HttpClient())

    {

        byte[] result = await httpClient.GetByteArrayAsync(uri);

        Cache.Add(uri, result);

        return result;

    }

}

It first checks the cache, if the data is already cached for the specified URI, then it outputs the cached data directly, no async operation is needed. However, at compile time, since the function has the async modifier, the entire function body, including the if statement, is compiled into an async state machine. At runtime, a task is always allocated on the heap and should be garbage collected, and the async state machine is always started, even when the result is available in the cache and no async operation is needed. With ValueTask<TResult>, this can be easily optimized:

internal static ValueTask<byte[]> DownloadAsyncValueTask(string uri)

{

    // Not compiled to async state machine. When URI is cached, no async state machine is started.

    return Cache.TryGetValue(uri, out byte[] cachedResult)

        ? new ValueTask<byte[]>(cachedResult)

        : new ValueTask<byte[]>(DownloadAsync());

    async Task<byte[]> DownloadAsync()

{

        // Compiled to async state machine. When URI is not cached, async state machine is started.

        using (HttpClient httpClient = new HttpClient())

        {

            byte[] result = await httpClient.GetByteArrayAsync(uri);

            Cache.Add(uri, result);

            return result;

        }

    }

}

Now the function becomes a sync function with awaitable ValueTask<TResult> output. When the result is available in the cache, the async local function is not called, so there is no async operation or async state machine involved, and there is no task allocated on heap. The async operation is encapsulated in the async local function, which is compiled to async state machine, and is only involved when the result is not available in the cache. As a result, the performance can be improved, especially when the cache is hit frequently.

Anonymous async function

The async and await keywords can be used with the lambda expression syntax for anonymous function. Just like named async function, anonymous async function’s output type is task:

internal static async Task AsyncAnonymousFunction(string readPath, string writePath)

{

    Func<string, Task<string>>readAsync = async path =>

    {

        using (StreamReader reader = new StreamReader(new FileStream(

            path: path, mode: FileMode.Open, access: FileAccess.Read,

            share: FileShare.Read, bufferSize: 4096, useAsync: true)))

        {

            return await reader.ReadToEndAsync();

        }

    };

    Func<string, string, Task>writeAsync = async (path, contents) =>

    {

        using (StreamWriter writer = new StreamWriter(new FileStream(

            path: path, mode: FileMode.Create, access: FileAccess.Write,

            share: FileShare.Read, bufferSize: 4096, useAsync: true)))

        {

            await writer.WriteAsync(contents);

        }

    };

    string result = await readAsync(readPath);

    await writeAsync(writePath, result);

}

The above async lambda expressions are compiled as methods of closure class, in the same pattern as normal sync lambda expressions.

Since task can be constructed with anonymous function with any output type, it can be constructed with anonymous async function with task output too:

internal static async Task ConstructTaskWithAsyncAnonymousFunction(

    string readPath, string writePath)

{

Task<Task<string>> task1 = new Task<Task<string>>(async () =>

        await ReadAsync(readPath));

   task1.Start(); // Cold task needs to be started.

    string contents = await task1.Unwrap(); // Equivalent to: string contents = await await task1;

 

    Task<Task> task2 = new Task<Task>(async () => await WriteAsync(writePath, null));

   task2.Start(); // Cold task needs to be started.

    await task2.Unwrap(); // Equivalent to: await await task2;

}

The first task is constructed with anonymous async function of type () –> Task<string>, so the constructed task is of type Task<Task<string>>. Similarly, the second task is constructed with anonymous async function of type () –> Task, so the constructed task is of type Task<Task>. As fore mentioned, nested task can be unwrapped and awaited.

internal static async Task RunAsyncWithAutoUnwrap(string readPath, string writePath)

{

    Task<string> task1 = Task.Run(async () => await ReadAsync(readPath)); // Automatically unwrapped.

    string contents = await task1; // Hot task.

 

    Task task2 = Task.Run(async () => await WriteAsync(writePath, contents)); // Automatically unwrapped.

    await task2; // Hot task.

}

Asynchrnous sequence: IAsyncEnumerable<T>

In C# 8.0, an async function can return a sequence of values using the return type IAsyncEnumerable<T>:

namespace System.Collections.Generic

{

    public interface IAsyncEnumerable<out T>

    {

        IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);

    }

 

    public interface IAsyncEnumerator<out T> : IAsyncDisposable

    {

        T Current { get; }

 

        ValueTask<bool> MoveNextAsync();

    }

}

The following example define a async function that generates a sequence of values by using yield statement:

internal static async IAsyncEnumerable<string>DownloadAsync(IEnumerable<Uri> uris)

{

    using WebClient webClient = new WebClient();

    foreach (Uri uri in uris)

    {

        string webPage = await webClient.DownloadStringTaskAsync(uri);

        yield return webPage;

    }

}

The following example consumes an async sequence by pulling the values asynchronously using async foreach statement:

internal static async void PrintDownloadAsync(IEnumerable<Uri> uris)

{

    IAsyncEnumerable<string> webPages = DownloadAsync(uris);

    await foreach (string webPage in webPages)

    {

        Trace.WriteLine(webPage);

    }

}

In above 2 async functions, the code are both compiled to a state macine similar to async functions returning a task.

async using declaration: IAsyncDispose

In C# 8.0, an instance can be decared with async using keywords, if its type implements System.IAsyncDispose interface:

namespace System

{

    public interface IAsyncDisposable

    {

        ValueTask DisposeAsync();

    }

}

For example, FileStream type implements IAsyncDispose:

internalstaticasyncvoid AsyncUsing(string file)

{

    awaitusing FileStream fileStream = File.OpenRead(file);

    Trace.WriteLine(fileStream.Length);

}

The code in the above async function is also compiled to a state machine.

Summary

C# supports async function as a simplified asynchronous programming model. In C#, async operation is represented by Task or Task<TResult>. A task can be async function output type, and can be used in await expression. Besides task, any type following the awaitable-awaiter pattern can be used in await expression. Async function is compiled to async state machine. It can capture the caller’s runtime context, and execute the continuation code with the captured context. C# generalizes async function output type with async method builder support, and a ValueTask<TResult> type is provided to improve async function performance. C# also support async local function and async anonymous function.

 

176 Comments

  • Indeed, async tasks are very useful not just in C# but with every form of modern application.

  • thanks for shared wonderful information of giving best information.its more useful and more helpful. great doing keep sharing

  • ```
    Task<Task<string>> task1 = new Task<Task<string>>(async () => await ReadAsync(readPath));
    string contents = await task1.Unwrap(); // Equivalent to: string contents = await await task1;
    ```

    and

    ```
    Task<string> task1 = Task.Run(async () => await ReadAsync(readPath)); // Automatically unwrapped.
    string contents = await task1;
    ```
    are not equivalent.
    Second example immediately starts the task, but first no.

    I got dead lock for first example.

    ````
    static void Main(string[] args)
    {
    Console.WriteLine(_test().Result);
    }
    static async ValueTask<int> _test()
    {

    Func<Task<Int32>> func = async () =>
    {
    await Task.Delay(1000);
    return 1;
    };
    Task<Task<Int32>> t1 = new Task<Task<int>>(func);
    var t2 = t1.Unwrap();
    await t2;
    //------------------------
    Console.WriteLine(''.....................sorry, dead lock");

  • @lobster Thank you for finding this bug. But this is not a deadlock. It is cold task not been started. I should call task1.Start() and task2.Start(). Fixed in the article. Thanks!

  • This is very good post I have read and I must appreciate you to have written this for us.Its really informative.

  • This is very good post I have read and I must appreciate you to have written this for us.Its really informative

  • Very helpful information. Thanks for sharing this with us.

  • Can we use this C program as a helper in the printing shop means to automate my work?

  • Thanks for sharing.I found a lot of interesting information here. A really good post, very thankful and hopeful that you will write many more

  • Really nice style and perfectly written content material in this. Material on this page is very efficient I’ve ever had. We do not need anything else. Thank you so much for the information.

  • This post is really astounding one! I was delighted to read this, very much useful. Many thanks

  • This post is really astounding one! I was delighted to read this, very much useful. Many thanks

  • This article is really fantastic and thanks for sharing the valuable post.

  • awesome post. I’m a normal visitor of your web site and appreciate you taking the time to maintain the nice site. I’ll be a frequent visitor for a long time.

  • Great Article it its really informative and innovative keep us posted with new updates. its was really valuable. 

  • This post is truly inspiring. I like your post and everything you share with us is current and very informative

  • Very interesting topic will bookmark your site to check if you Post more about in the future.

  • Thanks for sharing the awesome information!

  • Thanks for sharing, this is a fantastic blog post.Thanks Again. Want more.

  • Thanks for sharing this valuable article with us . I would like to share my site with you.

  • ok

  • The process of designing a website professionally is crucial to the development of web and any other graphic content. By breaking the entire design into smaller, manageable chunks helps architects, thinkers, and artists tackle their work with clarity and imagination. Our designers have devised an effective design strategy after enduring long and exhausting hours. We've been through the pain of beginning without knowing how to navigate the bricks of yellow. This is a brief summary of our design process.

  • C-sharp related wide discussion & data found quite effective & efficient manner of understanding & guidlines

  • Mehdi Salehi – “Delo Mibari” > New Song > Download With Text And 320 & 128 Links In Musico

  • Download New Music BY : Ahmad Saeedi | Hey Ravani With Text And 2 Quality 320 And 128 On Music-fa

  • Download New Music BY : Mohamad Zeyni Vand | Mishe Paeiz With Text And 2 Quality 320 And 128 On Music-fa

  • Great understanding of C# functional programming. Was looking out for this information from a long time. Very helpful for all the developers and programmers out there. For best online class help services , online exam help , online course help or any other educational help , please visit our website great online class help.




  • All your hard work is much appreciated. This content data gives truly quality and unique information. I’m definitely going to look into it.

  • Finally, I found the information one for whom I had been looking for two days. You know, today, if I had not found this information, then I was about to search for a cheap and best assignment writing website that could complete my assignment in a very short time.

  • It was great and interesting, thank you for your good site
    <a href="https://nilanet.com" >نیلانت</a>

  • like it
    <a href="https://amirmrseo.allblog.ir" >همه بلاگ سئو</a>

  • <a href='https://medical-phd.blogspot.com/'>Medical ph.d</a>
    <a href="https://ma-study.blogspot.com/">Masters Study</a>

  • That was wonderful information shared. Awaiting for more posts like this.

  • I just got to this amazing site not long ago. I was actually captured with the piece of resources you have got here. Big thumbs up for making such wonderful blog page!

  • tests your musical knowledge and sense of rhythm. Girlfriend is the happy girl sitting on top of the boom box. She is a white woman with long hair, wearing a red dress and high heels. Can you conquer her?

  • Download New Music, (Ahang) BY : Raashid Khorshid Khanoom Ghashange/ And lyrics

  • All your hard work is much appreciated. This content data gives truly quality and unique information. Programming is bit hard and tough but when you practically do it will be very easy for us and you. Thank you for this great blog.

  • This is very good post I have read and I must appreciate you to have written this for us.Its really informative

  • Thank you for sharing this blog this is very informative. <a href="https://www.autorepairgarland.com/Brake-services-garland-texas-usa">Brake Services</a>

  • Perturbed by the thoughts of your online exams? Do Exams For Me is your one-stop solution for all the worries. Just ask us to take my online exam for me.

  • The biggest problem for me was not getting any results regarding workflow output, but I am glad that you have written a useful post that will help me to draw conclusions.

  • <a href="https://ariamag.com/689/%D9%85%D8%AA%D9%86-%D8%B9%D8%A7%D8%B4%D9%82%D8%A7%D9%86%D9%87">عاشقانه</a> - <a href="https://roozaneh.net/fun/sms/%D8%AC%D9%85%D9%84%D8%A7%D8%AA-%D8%A7%D9%86%DA%AF%DB%8C%D8%B2%D8%B4%DB%8C-%D8%AE%DB%8C%D9%84%DB%8C-%DA%A9%D9%88%D8%AA%D8%A7%D9%87/">جملات انگیزشی کوتاه</a> - <a href="https://www.topnaz.com/succeeding-motivational-sentences/">متن انگیزشی</a>- <a href="https://musiceto.com/%DA%AF%D9%84%DA%86%DB%8C%D9%86-%D8%A2%D9%87%D9%86%DA%AF-%D8%B4%D8%A7%D8%AF-%D8%A7%DB%8C%D8%B1%D8%A7%D9%86%DB%8C/">آهنگ شاد</a> - <a href="https://gahmusic.com/%D8%B1%DB%8C%D9%85%DB%8C%DA%A9%D8%B3-%D8%B4%D8%A7%D8%AF-%D8%A7%DB%8C%D8%B1%D8%A7%D9%86%DB%8C-%D9%88-%D8%AE%D8%A7%D8%B1%D8%AC%DB%8C/">ریمیکس</a>

  • Posting an appropriately blurred out photo, the Isn't It Romantic actress—who was born and raised in Sydney, Australia—wrote, "Wow. The best birthday present just arrived!

  • In addition to sharing her new U.S. resident status, Rebel also took to her Story to post photos of the gorgeous floral

  • The red, pink and purple bouquet from Hailee featured a small note that read, "Happy birthday Reb! I wishThe red, pink and purple bouquet from Hailee featured a small note that read, "Happy birthday Reb! I wish

  • I could be there to celebrate with you. I hope you enjoy your day to the fullest. Love you! XO, Hailee.

  • But Hailee wasn't the only Pitch Perfect alum who joined in on Rebel's birthday fun.But Hailee wasn't the only Pitch Perfect alum who joined in on Rebel's birthday fun.

  • The actress later shared an image from her birthday dinner at Nobu in Malibu, Calif., that saw her smiling

  • On the image, Rebel also added in an adorable sticker that read, "Crush it like a Bella."On the image, Rebel also added in an adorable sticker that read, "Crush it like a Bella."

  • Sharing a video of her and her pals on their recent vacation to Cabo San Lucas for her birthday, Rebel reflected

  • writing, "The older I get the more I appreciate and understand the importance of great friendships."

  • I used to focus so much on work and crushing it out in the world, I was a lone wolf for most of it, I came to

  • C# is still worth learning in 2022 because of its versatility and adaptability. C# can be used in every type of software development, from mobile apps to video games to enterprise software.

  • Thank you for sharing a good opinion. Your opinion made me come up with a good idea. I hope you can get a good idea for a similar topic on my blog. I'll leave my blog address below.

  • Thank you for writing this great book. I really liked everything. And I favoriteized your blog to read the new content you posted.I would like to recommend a good topic if you wrote it. I'll leave my blog address below, so come and check it out.

  • Thank you for the definite information. They were really helpful to me, who had just been put into related work. And thank you for recommending other useful blogs that I might be interested in. I'll tell you where to help me, too. <a href="https://hostfiles.org/" target="_blank">메이저토토</a>

  • Woo Sang-hyuk enjoyed the joy of becoming a "world champion" with applause from all spectators who visited Stark Arena. Jean-Marco Tamberry, who tied for first place at the Tokyo Olympics, also greeted

  • Woo Sang-hyuk passed the 2m20, 2m24, and 2m28 in the first round. Woo Sang-hyuk and Roik Gashu were the only players who never failed and exceeded 2m28. Woo Sang-hyuk touched the bar in the first

  • Woo Sang-hyuk confessed, "Honestly, I was nervous, too," adding, "But I still believed in the time I had trained with coach Kim Do-kyun. I tried to keep my composure as much as possible." Woo Sang-hyuk

  • Victor Ahn, who participated as a technical coach for the Chinese national team at the 2022 Beijing Olympics, returned to Korea and expressed his feelings. Asked if he intends to be a leader for Korean

  • Victor Ahn said in an interview with Yonhap News Agency on the 19th, "Korea is the place where I have been loved for the longest time," adding, "If I am given any position or position, I will do my best at home

  • His contract with the Chinese national team expired after the Beijing Olympics. Shortly after the tournament, he was offered a four-year long-term contract by another foreign national team, but he did not

  • Victor Ahn said, "Since I left for China in 2020, I have never seen my family in Korea due to the spread of the novel coronavirus infection, and I thought I should be faithful to the role of my father and husband

  • Assignment is a new norm of every modern education system thus creating a need of Assignment Helper and Assignment help websites. We are one of those who have been providing such help to students for years. A main reason behind our success is our trustworthiness. Trust us once with the most important part of your academic life and see how we make it shine.

  • Excellent blog you’ve got here.. It’s difficult to find premium quality writing like yours these days.
    I seriously appreciate people just like you! Be cautious!!

  • Hi there to every one, the contents existing at this web site are truly amazing for people knowledge, well, keep up the good work fellows.

  • Say no to paying tons of cash for overpriced Facebook advertising! Let me show you a platform that charges only a very small payment and provides an almost endless volume of web traffic to your website.

  • I have recently started a web site, the information you provide on this site has helped me tremendously. Thank you for all of your time & work.

  • Very nice article and straight to the point. I don’t know if this is truly the best place to ask but do you folks have any idea where to get some professional writers? Thank you.

  • Wonderful post with amazing article. This post was very well written, and it also contains a lot of useful facts that is useful in our life. Thanks

  • It's a pleasure to visit your weblog again. It's been a few months. Nicely read the long-awaited article. This post is needed to sum up my assignments at the university and has the same topic as yours. Thank you.

  • I no uncertainty esteeming each and every bit of it. It is an amazing site and superior to anything normal give. I need to grateful. Marvelous work! Every one of you complete an unfathomable blog, and have some extraordinary substance. Keep doing stunning 메이저사이트순위
    https://kipu.com.ua/

  • Self-construction

  • Royalcasino487

  • Friday Night Funkin is a new game and created by ninja_muffin99 and some others. This game is a perfect combination of sound and graphics. I feel very happy playing it every day.

  • KBH games is one of the best websites today where we can play many games online. I often visit this site to play FNF (A game that is receiving the most love right now) If you are interested, please try it on this site.

  • علت خراب شدن لاک ناخن ممکنه به عوامل زیادی چون: شرایط محیطی، تاریخ تولید و تاریخ انقضا، بسته بندی لاک، مواد سازنده و…. بستگی داشته باشه.
    برای کمک به افزایش ماندگاری و کیفیت لاک بهتره، لاک‌ها را در مکان‌های تاریک و خشک مانند کمد یا کشو نگهداری کنید. برای مطالعه بیشتر درمورد علت خراب شدن لاک ناخن با بازرگانی گلارا همراه باشید.

  • Please keep on posting such quality articles as this is a rare thing to find these days. I am always searching online for posts that can help me. watching forward to another great blog. Good luck to the author! all the best! 스포츠토토사이트

  • خرید ارزان قهوه فرانسه با بالاترین کیفیت در فروشگاه اینترنتی موریس
    قیمت قهوه فرانسه
    خرید ارزان قهوه فرانسه
    ....و

  • Call of DutyUltimate Edition کاملا اوریجینال
    کیفیت تضمین شده
    به تازگی بازی Call of Duty: Vanguard که توسط استودیو Sledgehammer توسعه یافته است، با انتشار یک تریلر معرفی شد و همچنین در مراسم گیمزکام شاهد نشر یک کلیپ ویدیو ۱۰ دقیقه‌ای با محوریت قسمت داستانی آن بودیم. در این کلیپ ویدیو می‌توانیم آغاز روند داستان کاراکتر پالینا با بازی لارا بیلی را که براساس کاراکتری واقعی به نام لیودمیلا پاولیچنکو خلق شده به تماشا بنشینیم . این کلیپ ویدیو با به تصویر کشیدن قسمت آغازین روند داستانی این شخصیت ویژگی‌های جدید گان ‌پلی و بعضی محیط ‌های این بازی را نشان می‌دهد.

  • بازی های انلاین و خرید گیم تایم 60 روزه برای بازی دوست داشتنی وارکرافت و انواع بازی های مهیج دیگر چون کالاف دیوتی و دیابلو و سایر محصولات بلیزارد در فروشگاه جت گیم

  • Pioneering innovative digital experiences that fuel startups, scale-up enterprises, and help family businesses embrace digital transformation.

  • www.google.es/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.it/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.it/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.com.br/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.br/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.com.br/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.ca/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.ca/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.com.hk/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.nl/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.co.in/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.ru/url?sa=t&url=https://www.outsourcetoasia.io
    www.innerdesign.com/blog/architecture/Google-Campus
    www.google.pl/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.au/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.tw/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.co.id/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.ch/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.ch/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.ch/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.be/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.be/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.cz/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.co.th/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.com.ua/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.com.tr/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.mx/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.dk/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.hu/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.fi/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.co.nz/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.co.nz/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.co.nz/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.vn/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.pt/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.ro/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.my/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.co.za/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.sg/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.co.il/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.cl/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.ie/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.sk/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.pe/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.ae/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.com.pk/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.com.co/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.co/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.com.eg/url?sa=t&url=https://www.outsourcetoasia.io
    maps.google.lt/url?sa=t&url=https://www.outsourcetoasia.io
    www.google.com.sa/url?sa=t&url=https://www.outsourcetoasia.io
    images.google.ee/url?sa=t&url=https://www.outsourcetoasia.io

  • Nothing captures attention like animation. Animation is one medium that if done correctly can be the best marketing you can do, this why our experts have made sure that when finish your animation, you are in awe of it.

  • خریدshadowlands ، شدولند مکانی است که تمام مردگان و ارواح به آنجا سفر میکنند . سیلواناس به رهبری هلیا ، گروهی برای مبارزه تشکیل داده . سیلوانا قصد دارد در دنیای آزروت ، تمام موجودات را نابود و به شدولند ببرد .
    تمام قدرت سیلوانا به خاطر شخصی است که با او در ارتباط است . سیلوانا Domination را شکست میدهد و مرز بین آزروت و شدولند ( زندگی بعد از مرگ ) را از بین میبرد .امپراطوری سیاه بر پا خواهد شد . جنگجویان بعد از کشتن هر قربانی ، روحشان را به شدولند میفرستند . الینس ها با خدایان در شدولند میجنگند . هاکار قصد دارد تا با بلعیدن رواح قدرتمند شود .
    سیلواناس با هدایت جنگ ، قصد دارد ارواح را از هاکار به سمت هلیا هدایت کند . ارواح تمام ترول هایی که در تزمیر کشته شده اند ، در موربارا ساکن اند . در النیر ریفت‌لندز بیشترین فساد وید در دراگون آیلز و رویای زمردین وجود دارد . شهر و قرارگاه اصلی به عنوان منطقه آرام در ژیروس قرار دارد . این ناحیه دارای مقدار خیلی زیادی از کؤست لاین و دو دانجن میباشد . در مجموع 9 دانجن که 5 تا در شدولند و بقیه در دراگون ایلز است و اسمی ندارد .

  • Nothing captures attention like animation. Animation is one medium that if done correctly can be the best marketing you can do, this why our experts have made sure that when finish your animation, you are in awe of it. professional Agency

  • گیم تایم 60 روزه در حال حاضر تنها گیم تایمی است که از طرف کمپانی blizzard برای بازیکنان گیم ، ورد اف وارکرافت ارائه شده است. در گذشته گیم تایم هایی مانند 30 روزه و 180 روزه هم موجود بود اما به دلیل سیاست های جدید این کمپانی و خط مشی که در نظر گرفته است، تنها گیم تایمی که در حال حاضر امکان فراهم کردن آن برای گیمر های عزیز، گیم تایم 60 روزه می باشد. در ادامه توضیحات جالبی در مورد گیم تایم برای شما جمع آوری کرده ایم که خواندنشان خالی از لطف نیست.

    کاربرد گیم تایم دو ماهه

    در حال حاضر گیم تایم 2 ماهه در تمامی زمینه های world of warcraft کاربرد دارد. اما اگر می خواهید که یک سری تجربه های جذاب و جدید را تجربه کنید باید این گیم تایم را خریداری کنید. این تجربه ها عبارتند از:
    استفاده از اکسپنشن های جدید
    بازی در مپ های جدید
    لول آپ به سبک جدید
    تغییر در شکل بازی

  • very gooood.✔✔✔✔✔

  • An intriguing discussion may be worth comment. I’m sure you should write much more about this topic, may well be described as a taboo subject but generally folks are too little to chat on such topics. An additional. Cheers

  • There are some things to supplement, but I think your opinion is definitely worth considering. In some areas, innovative ideas that no one has ever come up with stand out. I would like to share more opinions with you on this topic.

  • برخی از بازی های  شرکت بلیزارد بصورت رایگان دردسترس گیمرها و کاربران نخواهد بود. و این کاربران برای استفاده از بازی  گیم تایم یا همان گیم کارت خریداری کنند. یکی از این بازی ها،‌ بازی محبوب و پرطرفدار ورلدآف وارکرافت است. به شارژ ماهیانه بازی وارکرافت در سرورهای بازی بلیزارد  گیم تایم می گویند ، که در فروشگاه جت گیم موجود می باشد.

    خرید گیم تایم 60 روزه ازفروشگاه جت گیم:

    در واقع گیم تایم 60 روزه نمونه ای جدید است از گیم تایم ها برای استفاده دربازی World of Warcraft  . که در ادامه بیشتر در مورد این محصول و نحوه استفاده از آن توضیح می دهیم .

    شما با خرید گیم تایم 60 روزه در مدت زمان آن گیم تایم ( 60 روز ) به امکاناتی در بازی World of Warcraft درسترسی پیدا خواهید کرد که این امکانات شامل موارد زیر میباشند :

    1 - اجازه لول آپ کردن تا لول 50 ( بدون گیم تایم فقط می توانید تا لول 20 بازی کنید )

    2 - اجازه  چت کردن با دیگران درون بازی ( بدون گیم تایم نمی توانید در بازی  چت کنید )

    3 - دسترسی به بازی World of Warcraft Classic

  • Thanks for your sharing.

  • Your content is great. I enjoyed reading the articles contained on this site.

  • خریدshadowlands

    خریدshadowlands ، شدولند مکانی است که تمام مردگان و ارواح به آنجا سفر میکنند . سیلواناس به رهبری هلیا ، گروهی برای مبارزه تشکیل داده . سیلوانا قصد دارد در دنیای آزروت ، تمام موجودات را نابود و به شدولند ببرد .
    تمام قدرت سیلوانا به خاطر شخصی است که با او در ارتباط است . سیلوانا Domination را شکست میدهد و مرز بین آزروت و شدولند ( زندگی بعد از مرگ ) را از بین میبرد .امپراطوری سیاه بر پا خواهد شد . جنگجویان بعد از کشتن هر قربانی ، روحشان را به شدولند میفرستند . الینس ها با خدایان در شدولند میجنگند . هاکار قصد دارد تا با بلعیدن رواح قدرتمند شود .
    سیلواناس با هدایت جنگ ، قصد دارد ارواح را از هاکار به سمت هلیا هدایت کند . ارواح تمام ترول هایی که در تزمیر کشته شده اند ، در موربارا ساکن اند . در النیر ریفت‌لندز بیشترین فساد وید در دراگون آیلز و رویای زمردین وجود دارد . شهر و قرارگاه اصلی به عنوان منطقه آرام در ژیروس قرار دارد . این ناحیه دارای مقدار خیلی زیادی از کؤست لاین و دو دانجن میباشد . در مجموع 9 دانجن که 5 تا در شدولند و بقیه در دراگون ایلز است و اسمی ندارد .

  • 60 days game time is currently the only game time provided by blizzard for gamers, Word of Warcraft. In the past, there were games like 30-day and 180-day, but due to the new policies of this company and the policy that it has considered, the only game time that is currently possible for dear gamers is Game Time 60. Is fasting. In the following, we have collected interesting explanations about game time for you, which are worth reading.

    Two months gametime application

    Currently, 2 months gametime is used in all areas of world of warcraft. But if you want to experience a series of exciting and new experiences, you have to buy this game time. These experiences include:
    Use new expansions
    Play in new maps
    Roll up in a new style
    Change in the shape of the game
    Prepared from the site of Jet Game

  • Thank you for sharing information …

  • Two-month gametime popularity:
    As mentioned above, 60-day gametime has been more popular than other gametime for a few months. This is because it has both the right time and the right price. The reason world of warcraft players use this type of game time is because of the time. Because 60 days of game time is an average game time and most people use the days of this game time. One of the advantages of this game time over other game times is its length of time.

    Types of game time regions
    In general, the two-month game time is made up of 2 regions, Europe and America. But an important argument is that it is recommended to get a gametime regimen that is compatible with your Shodland region. If you are looking for our advice, we recommend that you buy the European region. Because it is close to Middle East servers and you usually get a better ping. For preparation, refer to Jet Game website.

  • Five Nights at Freddy's is a horror game. And I really enjoyed participating in this game. How about you?

  • Are you ready to find words hidden in crosswords at Wordle game? This game is very good for players because it can help increase your memory and good for your logic ability. Wordle now has a children's version. It's great isn't it?

  • Thank you for sharing information …

  • Some Blizzard games will not be available to gamers and users for free. And these users to buy game time or the same game card. One of these games is محب the popular World of Warcraft game. The monthly charge of Warcraft game on Blizzard Game Time game servers, which is available in the Jet Game store.

    Buy 60-day game time from Jet Game store:

    In fact, 60-day game time is a new example of game time for use in World of Warcraft. In the following, we will explain more about this product and how to use it.

    By purchasing 60 days of game time during that game time (60 days), you will have access to features in World of Warcraft, which include the following:

    1 - Permission to roll up to level 50 (without game time you can only play up to level 20)

    2 - Permission to chat with others in the game (without game time you can not chat in the game)

    3 - Access to the game World of Warcraft Classic

  • very nice

  • Some Blizzard games will not be available to gamers and users for free.

  • I really like and appreciate your blog.Thanks Again. Awesome.

  • Quality articles or reviews is the key to interest the users to go to see the web page,
    that’s what this website is providing.

  • This post is in fact a fastidious one it helps
    new internet visitors, who are wishing in favor
    of blogging.

  • خرید بازی دراگون فلایت جت گیم  سری بازی ورلد آف وارکرافت یکی از قدیمی ترین گیم هایی است که هم از نظر محبوبیت و هم از نظر شکل بازی نزدیک به دو دهه است که با ارائه انواع بسته های الحاقی برای دوستداران این گیم سرپا است و به کار خود ادامه می دهد .
    ورلد آف وارکرافت توسط شرکت بلیزارد ارائه شده و بدلیل سبک بازی و گرافیک بالا در سرتاسر جهان طرفداران زیادی را به خود جذب کرده است.
    این بازی محبوب دارای انواع بسته های الحاقی میباشد که جدید ترین آن که به تازگی رونمائی شده و در حال حاضر صرفا امکان تهیه پیش فروش آن فراهم میباشد دراگون فلایت است
    این بازی که از نظر سبک بازی با سایر نسخه ها متفاوت بوده و جذابیت خاص خود را دارد که در ادامه به آن می پردازیم . همچنین برای تهیه نسخه های این گیم جذاب می توانید به سایت جت گیم مراجعه نمائید. در ادامه بیشتر در مورد بازی و سیستم مورد نیاز بازی می پردازیم

  • "We provide a safe major site. We introduce it after carefully reviewing only the verification know-how and safety for many years.
    All of these suppliers have been thoroughly tested. Don't worry now and use it.
    "

  • "If you don't know how to get started with Toto Analysis, you can easily find hundreds of analysis articles just by searching. Of course, all of these articles are unreliable and are for reference only.
    However, I hope that you will learn your own analysis method by referring to various sports analysis articles.
    "

  • "With the development of the Internet, the Toto site allows you to enjoy the Sports Toto game safely and conveniently 24 hours a day on your mobile device, regardless of time and place, anytime, anywhere.
    "

  • "
    Overseas betting sites have many advantages, such as safety, unlimited short pole betting, and unlimited deposits and withdrawals, which are different from domestic private casino sites. We recommend overseas casino sites where you can deposit won in Korean banks (casino services and sports betting available).
    "

  • گیم تایم 60 روزه در حال حاضر تنها گیم تایمی است که از طرف کمپانی blizzard برای بازیکنان گیم ، ورد اف وارکرافت ارائه شده است. در گذشته گیم تایم هایی مانند 30 روزه و 180 روزه هم موجود بود اما به دلیل سیاست های جدید این کمپانی و خط مشی که در نظر گرفته است، تنها گیم تایمی که در حال حاضر امکان فراهم کردن آن برای گیمر های عزیز، گیم تایم 60 روزه می باشد. در ادامه توضیحات جالبی در مورد گیم تایم برای شما جمع آوری کرده ایم که خواندنشان خالی از لطف نیست.

    کاربرد گیم تایم دو ماهه

    در حال حاضر گیم تایم 2 ماهه در تمامی زمینه های world of warcraft کاربرد دارد. اما اگر می خواهید که یک سری تجربه های جذاب و جدید را تجربه کنید باید این گیم تایم را خریداری کنید. این تجربه ها عبارتند از:
    استفاده از اکسپنشن های جدید
    بازی در مپ های جدید
    لول آپ به سبک جدید
    تغییر در شکل بازی

  • I have been looking for articles on these topics for a long time. <a href="http://images.google.com.bd/url?sa=t&url=https%3A%2F%2Fkeonhacai.wiki">baccarat online</a> I don't know how grateful you are for posting on this topic. Thank you for the numerous articles on this site, I will subscribe to those links in my bookmarks and visit them often. Have a nice day

  • Your writing is perfect and complete.Keonhacai However, I think it will be more wonderful if your post includes additional topics that I am thinking of. I have a lot of posts on my site similar to your topic. Would you like to visit once?

  • بک لینک ها به عنوان «پیوندهای درونی»، «پیوندهای ورودی» یا «پیوندهای یک طرفه» معروف هستند. بک لینک ها پیوندهایی هستند که از یک وب سایت به صفحه ای در وب سایت دیگر زده می شوند. گوگل و دیگر موتورهای جستجوی به بک لینک ها به عنوان یک اعتبار نگاه می کنند و صفحاتی که دارای تعداد زیادی بک لینک هستند، رتبه بالایی در موتورهای جستجو خواهند داشت.

  • Your content is great..

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking! baccaratcommunity

  • Your article has answered the question I was wondering about! I would like to write a thesis on this subject, but I would like you to give your opinion once :D <a href="http://google.cz/url?sa=t&url=https%3A%2F%2Foncainven.com">baccaratsite</a>

  • Seo Clinic is proud to perform site optimization in Isfahan or Isfahan site SEO in other parts of the country by using the principles and general rules of search engines to place your site in the initial results of Google search engine. We help you in choosing terms

  • Of course, your article is good enough, Keo nha cai but I thought it would be much better to see professional photos and videos together. There are articles and photos on these topics on my homepage, so please visit and share your opinions.

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking! bitcoincasino

  • خرید گیم تایم 60 روزه

     خرید گیم تایم 60 روزه: بدون شک همه ی دوستداران بازی های آنلاین چندین سال است که با نام بازی  ورلد آف وارکرافت آشنا هستند ، بازی وارکرافت یکی از بازی های پر طرفدار و جذاب در بین گیم های آنلاین چند نفره است که توسط شرکت بلیزارد ارائه شد.

  • خرید بازی دراگون فلایت جت گیم  سری بازی ورلد آف وارکرافت یکی از قدیمی ترین گیم هایی است که هم از نظر محبوبیت و هم از نظر شکل بازی نزدیک به دو دهه است که با ارائه انواع بسته های الحاقی برای دوستداران این گیم سرپا است و به کار خود ادامه می دهد .
    ورلد آف وارکرافت توسط شرکت بلیزارد ارائه شده و بدلیل سبک بازی و گرافیک بالا در سرتاسر جهان طرفداران زیادی را به خود جذب کرده است.
    این بازی محبوب دارای انواع بسته های الحاقی میباشد که جدید ترین آن که به تازگی رونمائی شده و در حال حاضر صرفا امکان تهیه پیش فروش آن فراهم میباشد دراگون فلایت است
    این بازی که از نظر سبک بازی با سایر نسخه ها متفاوت بوده و جذابیت خاص خود را دارد که در ادامه به آن می پردازیم . همچنین برای تهیه نسخه های این گیم جذاب می توانید به سایت جت گیم مراجعه نمائید. در ادامه بیشتر در مورد بازی و سیستم مورد نیاز بازی می پردازیم

  • I am so lucky that I've finally found this. 안전놀이터추천 This article has huge information for me, and well prepared for people who need about this. I am sure this must be comepletly useful. I am a persone who deals with this.

  • The content of this article is very good. I love it very much.

  • کاربرد گیم تایم دو ماهه

    در حال حاضر گیم تایم 2 ماهه در تمامی زمینه های world of warcraft کاربرد دارد. اما اگر می خواهید که یک سری تجربه های جذاب و جدید را تجربه کنید باید این گیم تایم را خریداری کنید. این تجربه ها عبارتند از:
    استفاده از اکسپنشن های جدید
    بازی در مپ های جدید
    لول آپ به سبک جدید
    تغییر در شکل بازی سایت جت گیم

  • this is a really detailed and lengthy info graphical article it needs time to understand for a non related field is much difficult to understand at once we hope for your next article to be a little easy.

  • خرید بازی دراگون فلایت جت گیم  سری بازی ورلد آف وارکرافت یکی از قدیمی ترین گیم هایی است که هم از نظر محبوبیت و هم از نظر شکل بازی نزدیک به دو دهه است که با ارائه انواع بسته های الحاقی برای دوستداران این گیم سرپا است و به کار خود ادامه می دهد .
    ورلد آف وارکرافت توسط شرکت بلیزارد ارائه شده و بدلیل سبک بازی و گرافیک بالا در سرتاسر جهان طرفداران زیادی را به خود جذب کرده است.
    تهیه از سایت جت گیم

  • Why couldn't I have the same or similar opinions as you? T^T I hope you also visit my blog and give us a good opinion. <a href="http://cse.google.com.br/url?sa=t&url=https%3A%2F%2Fmajorcasino.org">majorsite</a>

  • I was looking for another article by chance and found your article <a href="http://clients1.google.si/url?sa=t&url=https%3A%2F%2Foncainven.com">safetoto</a> I am writing on this topic, so I think it will help a lot. I leave my blog address below. Please visit once.

  • In fact, the 60-day game time is a new example of game times to be used in the World of Warcraft game. In the following, we explain more about this product and how to use it.

    By purchasing 60-day game time, during that game time (60 days), you will have access to features in the World of Warcraft game, which include the following:

    1- permission to level up to level 50 (without game time, you can only play up to level 20)

    2- permission to chat with others in the game (you cannot chat in the game without game time)

    3 - Access to the game World of Warcraft Classic
    Prepared from the Jet Game website

  • ر واقع گیم تایم 60 روزه نمونه ای جدید است از گیم تایم ها برای استفاده دربازی World of Warcraft  . که در ادامه بیشتر در مورد این محصول و نحوه استفاده از آن توضیح می دهیم .

    شما با خرید گیم تایم 60 روزه در مدت زمان آن گیم تایم ( 60 روز ) به امکاناتی در بازی World of Warcraft درسترسی پیدا خواهید کرد که این امکانات شامل موارد زیر میباشند :

    1 - اجازه لول آپ کردن تا لول 50 ( بدون گیم تایم فقط می توانید تا لول 20 بازی کنید )

    2 - اجازه  چت کردن با دیگران درون بازی ( بدون گیم تایم نمی توانید در بازی  چت کنید )

    3 - دسترسی به بازی World of Warcraft Classic

    در نتیجه برای بازی در World of Warcraft حتمآ به تهیه گیم تایم نیاز دارید.تهیه از سایت جت گیم

  • The 60-day game time is currently the only game time provided by the Blizzard company for the players of the game, Word of Warcraft. In the past, game times such as 30 days and 180 days were also available, but due to the new policies of this company and the policy it has considered, the only game time that is currently available for dear gamers is Game Time 60. It is fasting. In the following, we have collected interesting explanations about Game Time for you, which are worth reading.

    Game time application for two months

    Currently, 2-month game time is used in all areas of World of Warcraft. But if you want to experience a series of interesting and new experiences, you should buy this game time. These experiences include:
    Using new extensions
    Play on new maps
    Lollup in a new style
    Change in the shape of the game
    Prepared from the Jet Game website

  • From some point on, I am preparing to build my site while browsing various sites. It is now somewhat completed. If you are interested, please come to play with slotsite

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking!

  • If you have any questions about the safe eat-and-run guarantee company provided by Eat Tiger, please contact the customer center at any time!

    We will recommend and introduce each Toto site that suits the characteristics of each company and members. Make high profits through our eating tiger.

  • Popularity of Gametime for two months:
    As mentioned above, the 60-day game time has been more popular than other game times for several months. This is because it has both the right time and the right price. The reason why World of Warcraft players use this type of game time is the duration. Because the game time of 60 days is an average game time and most people use the days of this game time. One advantage that this game time has over other game times is its duration.

    All kinds of game time regions
    In general, the two-month game time is made from 2 regions, Europe and America. But an important point is that it is recommended to get a region of Gametime that is compatible with your Shadowland region. If you are looking for our advice, we recommend you to buy Region Europe. Because it is close to Middle East servers and usually you get better ping. Prepared from the Jet Game website

  • The 60-day game time is currently the only game time provided by the Blizzard company for the players of the game, Word of Warcraft. In the past, game times such as 30 days and 180 days were also available, but due to the new policies of this company and the policy it has considered, the only game time that is currently available for dear gamers is Game Time 60. It is fasting. In the following, we have collected interesting explanations about Game Time for you, which are worth reading.

    Game time application for two months

    Currently, 2-month game time is used in all areas of World of Warcraft. But if you want to experience a series of interesting and new experiences, you should buy this game time. These experiences include:
    Using new extensions
    Play on new maps
    Lollup in a new style
    Change in the shape of the game
    Prepared from the Jet Game website.

  • Buy 60 days game time from Jet Game

    If you are looking to buy a 60-day game time for your World of Warcraft game, you can visit the Jet Game store. One of the features of this store is that it is instant. After paying the price, the product code will be delivered to you as soon as possible. Currently, the advantage of the Jet Game store is that it is faster than other stores. And it is operating with an experienced staff and with the support of products provided to users at the most appropriate price.

    The best way to activate 60 days game time
    The easiest way and the best way to activate Gametime is to present it to the Battlenet client. A code will be sent to you after you purchase 60 days of game time from Jet Game. You must enter this code in the BattleNet client in the Redem a Code section to activate the 60-day game time for you. But another way to activate Gametime is to visit the Battlenet site.

    Gametime's connection to Shadoland
    From the very first day that Shdoland came to the World of Warcraft, Game Time was also presented. It can be said that the main purpose of connecting Gametime to Shadeland is to prevent cheating. Because you have to pay a lot of money to be able to play Game Time. On the other hand, it is to strengthen the servers. After the emergence of Gametime servers, Warcraft game servers have also become stronger.
    Prepared from the Jet Game website

  • معمولا فرانچایز ورلد آف وارکرافت خیلی وابسته به گرافیک نیست وبه همین دلیل حداقل سیستم مورد نیاز ورلد آف وارکرافت دراگون فلایت فرق چندانی با نسخه های قبلی خود ندارد.

    همچنین لازم به ذکر است در صورتی که شما سیستم قدرتمند داشته باشید قادر خواهید بود تا کیفیت گرافیکی بالاتری را نیز تجربه کنید وتغییر را
    تهیه از سایت جت گیم نسبت به دی ال سی های قبلی این نسخه احساس خواهید کرد.

  • If you have been a die-hard fan of the Diablo series, you have undoubtedly experienced both the Reaper of Souls and Rise of the Necromancer expansion packs many times, however, Blizzard is for those who want both of these expansion packs in one complete package. has prepared the Diablo III Eternal Collection title so that they can fully enjoy these games in one place, and even those who have already experienced these games can go to Diablo III Eternal Collection to refresh their memories. Surely, your first question about the expansion pack Diablo III Reaper of Souls Ultimate Evil Edition will be related to its story. If you have finished Diablo III, you will surely agree with us that the ending of the game left many questions unanswered and added many plot points to the Serini storyline. So by buying the game Diablo® IIIEternal Collection from the fascinating ending of the game, all the fans were confident that Blizzard will not make us wait for more than a decade this time to answer the questions and we will soon find the answer to most of our questions. The story part of Diablo III Eternal Collection takes place immediately after the adventures of the main game and starts with a very attractive Blizzard-style cinematic. Tyrael by regrouping
    Horadr tries to take the Soul Stone, which now holds the Great Demon, to a safe place and away from any creature. But immediately after reaching the desired point, the mysterious and former brother enters the story. Finally, it should be said that if you haven't managed to experience Diablo III yet, it is better to experience the main game before experiencing the Diablo III Eternal Collection add-on package. Prepared from the Jet Game site.

  • By purchasing 60-day game time, during that game time (60 days), you will have access to features in the World of Warcraft game, which include the following:

    1- permission to level up to level 50 (without game time, you can only play up to level 20)

    2- permission to chat with others in the game (you cannot chat in the game without game time)

    3 - Access to the game World of Warcraft Classic

    As a result, to play in World of Warcraft, you definitely need to prepare game time.

    Note 1: Game time or the time of the Word of Warcraft game is used for the ability to play online, and without Game Time you will not be able to play the popular game of Word of Warcraft.

    Tip 2: If you don't have game time, you can't play Word of Warcraft Classic, and you can
    Prepared from the Jet Game website.

  • I was looking for another article by chance and found your article I am writing on this topic, so I think it will help a lot. I leave my blog address below. Please visit once.
    http://clients1.google.co.il/url?sa=t&url=https%3A%2F%2Foncasino.io

  •  خریدبازیShadowlands Base اورجینال که میتوانید به 7 منطقه جدید بازی، کاوننت(Covenant)، دانجن(Dungeon) و رید(Raid) های جدید و بسیاری از آینم های جدید دیگر دسترسی پیدا کنند. که شامل7 منطقه جدید بازی به نام های Revendreath, Maldraxxus, Ardenweald, Bastion, The Maw, Korthia و در آخر Zereth Mortis که در آخرین بروزرسانی بازی به نام Eternity’s End اضافه شده است معرفی شدند. هر کدام از این منطقه های جدید در بازی می توانند مجموعه ای از رویداد های متفاوت و جذاب را برای گیمرها به همراه داشته باشند و تجربه ای بی نظیر برای آن ها ایجاد کند. چهار کاوننت بازی هم به نام های
    Night Fae, Venthyr, Kyria و Nercrolord در بازی وجود دارند و بازیکنان برای انتخاب و استفاده از آن ها آزادی عمل کامل خواهند داشت. علاقمندان به بازی جذاب World of Warcraft و همچنین کسانی که به دنبال کسب درآمد در این گیم هستند باید از هر فرصتی به نفع خودشان استفاده کنند و الان بهترین زمان برای تجربه این بازی هیجان انگیز است!
    همچنین با خریدبازیShadowlands Base از فروشگاه جت گیم میتوانید محصول خود را با قیمتی مناسب و در کمترین زمان ممکن تحویل بگیرید و خریدی مطمئن و بی دغدغه را تجربه کنید. جت گیم یکی از معتبرترین فروشگاه های بلیزارد درایران می باشد که دارای نماد اعتماد الکترونیکی از وزارتخانه های کشور میباشد تا شما با خیال آسوده به خرید خود بپردازید.

  • thanks

  • thanks

  • thanks

  • thanks

  • thanks

  • When I read an article on this topic, the first thought was profound and difficult, and I wondered if others could understand.. My site has a discussion board for articles and photos similar to this topic. Could you please visit me when you have time to discuss this topic?

  • nice discussion thank you

  • صفر تا صد صورت وضعیت نویسی تاسیسات برقی و مکانیکی ساختمان
    پروژه محور و صد در صد کاربردی
    سایت سودا
    sevdaa.ir

  • To use the async keywords in the thought this was a great, a method in C# may be turned asynchronously. An async method may have one or more await keywords. The suspension point is identified by the await keyword. Task, TaskT>, and void are all acceptable return types for async methods in C#.

  • Breaking Vegas Documentary: The True Story of The MIT <a href="https://mtt747.com/%EC%8B%A4%EC%8B%9C%EA%B0%84%ED%8C%8C%EC%9B%8C%EB%B3%BC">파워볼사이트 파워볼 커뮤니티</a> Team

  • All About <a href="https://mtline9.com">먹튀검증사이트</a> Program Had Helped Many Peoples To Earn Money

  • Wonderfully insightful and creative article. keep us informed of new developments. It was quite helpful.

  • Excellently interesting and original article. update us on new developments. It was quite beneficial.

  • Qi Coins Trick! Cheating The <a href="https://cnn714.com/%ED%86%A0%ED%86%A0%EC%82%AC%EC%9D%B4%ED%8A%B8">토토사이트</a>! - Stardew Valley

  • I think this is a very interesting content and it is getting a lot of attention. You can easily realize this when there are many members on the forum discussing your topic.

  • The article is very interesting and useful for all of us, I am here on this website to add more knowledge for myself.

  • The content contained in your article is very impressive for today's users. I'm very interested in this and if there are any new updates please let me know.

  • Your writing is perfect and complete. <a href="http://cse.google.ba/url?sa=t&url=https%3A%2F%2Fcasinonation.org/">safetoto</a> However, I think it will be more wonderful if your post includes additional topics that I am thinking of. I have a lot of posts on my site similar to your topic. Would you like to visit once?

  • It's the same topic , but I was quite surprised to see the opinions I didn't think of. My blog also has articles on these topics, so I look forward to your visit. safetoto

  • thanks for news

  • Excellent website!<a href="https://cebucasino.net/">세부카지노</a>

  • download music

  • I really delighted to find this internet site on bing, just what I was searching for

  • Thank you for the update, very nice site.

  • You have really shared a informative and interesting blog post with people..

  • Great post <a target="_blank" href="https://www.doracasinoslot.com">안전놀이터</a>! I am actually getting <a target="_blank" href="https://www.doracasinoslot.com">안전공원</a>ready to across this information <a target="_blank" href="https://www.doracasinoslot.com">검증사이트</a>, is very helpful my friend <a target="_blank" href="https://www.doracasinoslot.com">온라인슬롯</a>. Also great blog here <a target="_blank" href="https://www.doracasinoslot.com">온라인카지노</a> with all of the valuable information you have <a target="_blank" href="https://www.doracasinoslot.com">바카라사이트</a>. Keep up the good work <a target="_blank" href="https://www.doracasinoslot.com">온라인바카라</a> you are doing here <a target="_blank" href="https://www.doracasinoslot.com">토토사이트</a>. <a target="_blank" href="https://www.doracasinoslot.com"></a>

  • Great post <a target="_blank" href="https://www.erzcasino.com">안전놀이터</a>! I am actually getting <a target="_blank" href="https://www.erzcasino.com">안전공원</a>ready to across this information <a target="_blank" href="https://www.erzcasino.com">검증사이트</a>, is very helpful my friend <a target="_blank" href="https://www.erzcasino.com">온라인슬롯</a>. Also great blog here <a target="_blank" href="https://www.erzcasino.com">온라인카지노</a> with all of the valuable information you have <a target="_blank" href="https://www.erzcasino.com">바카라사이트</a>. Keep up the good work <a target="_blank" href="https://www.erzcasino.com">온라인바카라</a> you are doing here <a target="_blank" href="https://www.erzcasino.com">토토사이트</a>. <a target="_blank" href="https://www.erzcasino.com"></a>

  • thanks good news

  • HI THERE! THIS IS MY FIRST VISIT TO YOUR BLOG! WE ARE A GROUP OF VOLUNTEERS AND STARTING A NEW PROJECT IN A COMMUNITY IN THE SAME NICHE. YOUR BLOG PROVIDED US USEFUL INFORMATION TO WORK ON. YOU HAVE DONE A EXTRAORDINARY JOB!!!!! <a href="https://www.sportstotohot.com/" target="_blank" title="스포츠토토핫">스포츠토토핫</a>

  • HI THERE! THIS IS MY FIRST VISIT TO YOUR BLOG! WE ARE A GROUP OF VOLUNTEERS AND STARTING A NEW PROJECT IN A COMMUNITY IN THE SAME NICHE. YOUR BLOG PROVIDED US USEFUL INFORMATION TO WORK ON. YOU HAVE DONE A EXTRAORDINARY JOB!!!!!

  • Inni government, come up with new minimum wage policy...Labor is welcome

  • سایت جت گیم ارائه دهنده بازی های انلاین و کامپیوتر و پلی استیشن و همچنین مقالات جذاب میباشد
    از جت گیم دیدن فرموایید

Add a Comment

As it will appear on the website

Not displayed

Your website