Reputation: 19
I have a async method that is called when I need to update UI controls content, like this:
public async Task UpdateUI(int i)
{
Debug.WriteLine("Enter {0}", i);
DoSomethingSync(1000);
Debug.WriteLine("Await {0}", i);
await GetDataFromServerAsync(5000);
//Update UI Controls
Debug.WriteLine("Update {0}", i);
DoSomethingSync(2000);
Debug.WriteLine("Exit {0}", i);
}
public void DoSomethingSync(int delay)
{
Thread.Sleep(delay);
}
public Task GetDataFromServerAsync(int delay)
{
return TaskEx.Delay(delay);
}
When the data is changed on the server, I get a notification that come in different threads and I have to call a UpdateUI method on the UI thread, like this:
public async void OnServerDataChanged(int i)
{
Debug.WriteLine("WaitOne {0}", i);
_semaphore.WaitOne();
try
{
await Task.Factory.StartNew(async () =>
{
await UpdateUI(i);
}, CancellationToken.None, TaskCreationOptions.None, _scheduler);
}
finally
{
_semaphore.Release();
Debug.WriteLine("Release {0}", i);
}
}
I simulate multithreading notification:
public void SimulateMultiThreadingNotification()
{
TaskEx.Run(() =>
{
for (var i = 0; i < 3; i++)
{
TaskEx.Run(() =>
{
var id = Thread.CurrentThread.ManagedThreadId;
OnServerDataChanged(id);
});
}
});
}
Run:
_semaphore = new Semaphore(1, 1);
_scheduler = TaskScheduler.FromCurrentSynchronizationContext();
SimulateMultiThreadingNotification();
Output:
Enter 11
Await 11
Release 11
Enter 12
Await 12
Release 12
Enter 6
Await 6
Release 6
Update 11
Exit 11
Update 12
Exit 12
Update 6
Exit 6
How to synchronize so that the method executed sequentially like this:
Enter 11
Await 11
Update 11
Exit 11
Release 11
Enter 12
Await 12
Update 12
Exit 12
Release 12
Enter 6
Await 6
Update 6
Exit 6
Release 6
Advance thanx!
Edit: I found solution:
public void OnServerDataChanged(int i)
{
_semaphore.WaitOne();
try
{
var tcs = new TaskCompletionSource<bool>();
Task.Factory.StartNew(async () =>
{
await UpdateUI(i);
tcs.SetResult(true);
}, CancellationToken.None, TaskCreationOptions.None, _scheduler);
tcs.Task.Wait();
}
finally
{
_semaphore.Release();
Debug.WriteLine("Release {0}", i);
}
}
Edit 2: Solution by Yuval Itzchakov:
public void OnServerDataChanged(int i)
{
_semaphore.WaitOne();
try
{
Task.Factory.StartNew(async () =>
{
await UpdateUI(i);
}, CancellationToken.None, TaskCreationOptions.None, _scheduler).Unwrap().Wait();
}
finally
{
_semaphore.Release();
Debug.WriteLine("Release {0}", i);
}
}
Upvotes: 1
Views: 149
Reputation: 149588
Your problem is with the fact Task.Factory.StartNew
doesn't "handle" async Task
returning lambdas properly, since they generate a Task<Task>
, where you actually await
on the outter Task
, not the inner one.
You can use SemaphoreSlim
instead of a Semaphore
, it has a WaitAsync
which you can asynchronously wait on.
Calling Unwrap()
will solve your problem:
private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
public async Task ServerDataChangedAsync(int i)
{
Debug.WriteLine("WaitAsync {0}", i);
await _semaphore.WaitAsync();
try
{
await Task.Factory.StartNew(async () =>
{
await UpdateUI(i);
}, CancellationToken.None, TaskCreationOptions.None, _scheduler).Unwrap();
}
finally
{
_semaphore.Release();
Debug.WriteLine("Release {0}", i);
}
}
Also, don't do async void
, that's for event handlers. Do async Task
instead.
Upvotes: 1