How to synchronize resource access between UWP app and its background tasks?

筅森魡賤 提交于 2019-12-14 04:20:50

问题


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.


回答1:


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




回答2:


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.




回答3:


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


来源:https://stackoverflow.com/questions/35843812/how-to-synchronize-resource-access-between-uwp-app-and-its-background-tasks

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!