1

In WP7 apps I use a named Mutex to synchronize access to StorageFiles and Tiles. With the async code of UWP apps this is no longer robust because mutexes are thread-affine and mixed with async code this results in errors "Object synchronization method was called from an unsynchronized block of code".

Using mutex As New Threading.Mutex(False, "SyncAppAndBackTask") Try Await ... Finally mutex.ReleaseMutex() End Try End Using 

Using a SemaphoreSlim is not a option here because app and background tasks run in different processes.

This post suggests using Taks.Factory.StartNew with TaskCreationOptions.LongRunning or a StaTaskScheduler.

LongRunning does not solve the problem as my test code proves, see here. The versions of StaTaskScheduler I found use the Thread class which is not available in UWP.

Does someone have a solution for this or at least a UWP compatible version of StaTaskScheduler - in the post above Noseratio mentions "new Thread" could be replaced using Factory.StartNew.

As a workaround I currently use a storage file lock via .OpenAsync(FileAccessMode.ReadWrite) but this leads to ugly retry-loops.

0

3 Answers 3

0

If I understand you correctly, you are looking for and async lock. Take a look at http://asynclock.codeplex.com.

Sign up to request clarification or add additional context in comments.

1 Comment

No, I thought there was a general problem using a named mutex with async code. Your link does look interesting though. However these async locks do not seem be intended for x-process scenarios.
0

You get the exception, because you are not blocking the thread with mutex.WaitOne(), thus your mutex is not signaled and therefore your code is unsynchronized and you get exception:

Object synchronization method was called from an unsynchronized block of code

Try it like this:

Async Function TestMutex() As Task Using mutex As New Threading.Mutex(False, "SyncAppAndBackTask") Try mutex.WaitOne() For i = 1 To 3 Await SimulateCompute() Debug.WriteLine(i) Next Catch ex As Exception Debug.WriteLine(ex.Message) Throw Finally mutex.ReleaseMutex() Debug.WriteLine("success") End Try End Using End Function 

Also in your code you seem to be using TaskCountinuationOtions, whether in StartNew it should be TaskCreationOptions. Though, both are enums and LongRunning equal 2.

7 Comments

Thanks for your quick reply and fixing my code. Omitting mutex.WaitOne() only happened when shortening my production code for the test sample. With this fix my test no longer produces the exception mentioned.
Now I can no longer reproduce the exception. Simply using the mutex without any additional measures seems to work fine. I saw you posted a similar question Named Mutex with await and settled for a solution based on a custom TaskScheduler. I am no longer shure a problem synchronizing async file IO with a named mutex exists. What do you think?
@PeterMeinl In UWP direct thread managing is not possible, therefore I'm not sure now, how the solution with custom TaskScheduler would look like. Though, as I think, the LongRunning task should work - it dedicates single thread for a task and after returning from await it should be possible to safely release mutex. If your async operation is not long you may also consider synchronous waiting instead of awaiting after acquiring mutex. The best would be just to test it - maybe create a sample BTask, open a file and to the same in main UI (both with global mutex).
My current App does synchronizes the UI with a BackTask using a named mutex and seems to work fine. As a precaution it traces mutex .WaitOne(_preventHangingTimespan) timeouts and .ReleaseMutex exceptions and reports them via ApplicationInsights. But Stephen Cleary writes in WP8 Mutex - Resource Locking "You can't use thread-affine locks with async code" and this makes me wonder if I missed something.
@PeterMeinl The best would be to ask Stephen Cleary :) - maybe you can ensure if you can combine LongRunning task with mutex and awiat. IMO you can mix await and mutex, but you have to ensure that the same thread acquires and releases the mutex.
|
0

With the new Single Process Model of the Anniversary SDK syncing became much easier because background processing no longer runs in a different process. I am using the AsyncLock from Stephen Clearey's AsyncEx with it.

ORIGINAL ANSWER:

Statements like "Mutexes are thread-affine, so they don't work with async code" and bugs in my apps and tests had made me suspicious that we generally cannot use a named Mutex to synchronize resource access between an UWP app and its background tasks. All alternative solutions I saw only work in-process.

I came to the conclusion that mutexes do work fine in this scenario as long as .ReleaseMutex is coded on the same level as .WaitOne (e.g. not within an async method awaited after .WaitOne).

For coding convenience I encapsulated the mutex handling to allow Using statements:

'Usage to serialize access: Using New AppAndBackgroundMutex(TimeSpan.FromSeconds(5)) 'Access storage files 'Access ApplicationData settings 'Update Tiles End Using 'Usage to back out: Using New AppAndBackgroundMutex(TimeSpan.Zero) '... End Using Public NotInheritable Class AppAndBackgroundMutex : Implements IDisposable Private _mutex As Threading.Mutex Private _iOwnMutex As Boolean Sub New(waitTimeout As TimeSpan, Optional syncId As String = "SyncRates&Selections&Date") Const UniqePartOfMutexName = "<app specific GUID>" Try _mutex = New Threading.Mutex(False, UniqePartOfMutexName & syncId) _iOwnMutex = _mutex.WaitOne(waitTimeout) If Not _iOwnMutex Then Dim msg = ($"Unable to acquire mutex for app/background sync after waiting for {waitTimeout}.") If waitTimeout = TimeSpan.Zero Then 'Intentionally backing out Trace.Info(msg) Else Trace.Error(msg) End If Throw New MutexTimeoutException(msg) End If Catch ex As Threading.AbandonedMutexException Trace.Error("Abandoned Mutex detected! OS might have killed background task. Ignoring problem.") _iOwnMutex = True End Try End Sub 'Simple Dispose implementaion because class is sealed Public Sub Dispose() Implements IDisposable.Dispose If _iOwnMutex Then _mutex.ReleaseMutex() _ mutex.Dispose() End Sub End Class 

Alternatively one could use a file lock to back out:

Try Dim file = Await ApplicationData.Current.LocalFolder.CreateFileAsync("__AppAndBackgroundSync.lock", CreationCollisionOption.OpenIfExists) Await file.OpenAsync(FileAccessMode.ReadWrite) '... Catch ex As UnauthorizedAccessException Throw New AppAndBackgroundConcurrencyViolationException() End Try 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.