1

Hi i have textbox which has text changed event. Everytime a character is inserted in textbox the text changed event is fired. The Text changed event calls a async Task method. Below is the my event and async Task method.

public Textbox_TextChangedEvent()
    {
        GetStocks(texboxText);
    }

public async Task GetStocks(string texboxText)
    {
        IsBusy = true;
        await Task.Run(() => { CreateCollection(texboxText); });
        IsBusy = false;
    }

Question How can i make sure GetStocks method is called synchronously one after the other.

Example Suppose user has input Ted as input text. Then i want the the async call to be completed one after other. i.e it should call GetStocks in following order and also complete the task in following order itself.

  1. GetStocks(T)
  2. GetStocks(Te)
  3. GetStocks(Ted)
Vinay
  • 259
  • 4
  • 19

2 Answers2

0

To solve problems like this, we have used an AsyncLock previous projects. The AsyncLock will wait until the previous lock was released.

The AsyncLock may seem a little bit complicated first, but i hope the provided usage examples will illustrate its behaviour.

public class AsyncLock
{
   private TaskCompletionSource<object> _lastSection;

   public AsyncLock()
   {
      _lastSection = new TaskCompletionSource<object>();
      _lastSection.SetResult(null);
   }

   public class ReleaseLock : IDisposable
   {
      private readonly TaskCompletionSource<object> _tcs;

      public ReleaseLock(TaskCompletionSource<object> tcs)
      {
         _tcs = tcs;
      }

      public void Dispose()
      {
         _tcs.SetResult(null);
      }
   }

   /// <summary>
   /// Enters and locks a critical section as soon as the currently executing task has left the section.
   /// The critical section is locked until the returned <see cref="IDisposable"/> gets disposed.
   /// </summary>
   public Task<ReleaseLock> EnterAsync()
   {
      var newTcs = new TaskCompletionSource<object>();
      var toAwait = Interlocked.Exchange(ref _lastSection, newTcs);
      return toAwait.Task.ContinueWith((_) => new ReleaseLock(newTcs), TaskContinuationOptions.ExecuteSynchronously);
   }
}

You then can use await AsyncLock.EnterAsync() to wait until any previous lock was released. In the EnterAsync we queue the next Task after the current Task using ContinueWith. This means the await AsyncLock.EnterAsync() will be executed after the previous has finished.

using (await _lock.EnterAsync())
{
    // ...
}   

Here is an usage example:

class Program
{
   private static readonly AsyncLock _lock = new AsyncLock();

   private static async Task Test(int i, Task toComplete)
   {
      using (await _lock.EnterAsync())
      {
         await toComplete;
         Console.WriteLine(i);
      }
   }

   public static void Main(string[] args)
   {
      var tcs1 = new TaskCompletionSource<object>();
      var tcs2 = new TaskCompletionSource<object>();

      Task.Run(async () =>
      {
         var t1 = Test(1, tcs1.Task); // start first task
         var t2 = Test(2, tcs2.Task); // start second task

         tcs2.SetResult(null); // finish second first
         tcs1.SetResult(null); // fiish last task

         await Task.WhenAll(t1, t2); // will print: 1 and then 2
      }).Wait();
   }
}

The Test method takes will first enter the Async lock, then await the task toComplete and then write to the console.

We start two Test tasks ("1", and "2") and complete the second toComplete first. Without the AsyncLock the previous example prints: "2", "1". With the AsyncLock however the tasks are processed in the sequence they were started.

REMARKS: One last remark. This will achieve your processing order, but can be tricky sometimes. Using locks like this can easily lead to deadlocks which are hard to solve and harder to find in the first place. Use Locks very carefully.

EDIT: Here a usage example your your problem:

private readonly AsyncLock _lock = new AsyncLock();

public Textbox_TextChangedEvent()
{
    GetStocks(texboxText); // every call is now "queued" after the previous one
}

public async Task GetStocks(string texboxText)
{
    using(await _lock.EnterAsync())
    {
        IsBusy = true;
        await Task.Run(() => { CreateCollection(texboxText); });
        IsBusy = false;
    }
}
Kirill Shlenskiy
  • 9,367
  • 27
  • 39
Iqon
  • 1,920
  • 12
  • 20
  • That `ContinueWith` inside `EnterAsync` is strange. Passing in `newTcs` as state, only to have it ignored by the delegate? That feels like an attempt at avoiding closing over a local variable that was abandoned mid-way. Reminded me of this `AsyncLock` version (except this one actually uses the state): https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-6-asynclock/) – Kirill Shlenskiy Jul 27 '17 at 13:29
  • @KirillShlenskiy you are right. Got the code from an old Silverlight project, where this parameter was not optional. Will update the answer. – Iqon Jul 27 '17 at 13:41
-1

An easy option, depending on the situation could be:

public async Task GetStocks(string texboxText)
{
    Task.Run(() => { 
       IsBusy = true;
       CreateCollection(texboxText); 
       IsBusy = false;

    });
}
user230910
  • 2,353
  • 2
  • 28
  • 50
  • Sorry, the above code did not work for me. Actually the issue i am facing here is.In the order mentioned in my question, three async call are made,1.GetStocks(T) 2.GetStocks(Te) 3.GetStocks(Ted). But the sequence in which i get the result back is 3,2,1. But i want the result to come back in following order 1,2,3 – Vinay Jul 27 '17 at 13:12