如何在UWP应用程序及其后台任务之间同步资源访问?

时间:2016-03-07 12:30:18

标签: win-universal-app

在WP7应用程序中,我使用一个名为Mutex来同步对StorageFiles和Tiles的访问。 使用UWP应用程序的异步代码,这不再是健壮的,因为互斥体是线程仿射并与异步代码混合,这会导致错误“对象同步方法是从未同步的代码块中调用的”。

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

使用SemaphoreSlim不是一个选项,因为app和后台任务在不同的进程中运行。

post建议将Taks.Factory.StartNew与TaskCreationOptions.LongRunning或StaTaskScheduler一起使用。

LongRunning无法解决问题,因为我的测试代码证明了这一点,请参阅here。 我找到的StaTaskScheduler版本使用的是UWP中没有的Thread类。

有人有这个或至少是UWP兼容版本的StaTaskScheduler的解决方案 - 在Noseratio上面的帖子中提到“new Thread”可以使用Factory.StartNew替换。

作为一种解决方法,我目前通过.OpenAsync(FileAccessMode.ReadWrite)使用存储文件锁,但这会导致丑陋的重试循环。

3 个答案:

答案 0 :(得分:0)

如果我理解正确,您正在寻找和异步锁定。看看http://asynclock.codeplex.com

答案 1 :(得分:0)

您得到异常,因为您没有使用 mutex.WaitOne()阻止该线程,因此您的互斥锁未发出信号,因此您的代码未同步并且您将获得异常:

  

从非同步代码块

调用对象同步方法

试试这样:

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

同样在您的代码中,您似乎正在使用 TaskCountinuationOtions ,无论是在 StartNew 中,它都应该是 TaskCreationOptions 。虽然,两者都是枚举, LongRunning 等于2.

答案 2 :(得分:0)

使用Anniversary SDK的新单一流程模型,同步变得更加容易,因为后台处理不再在不同的流程中运行。我正在使用Stephen Clearey的AsyncEx的AsyncLock。

原始回答:

" Mutexes这样的语句是线程仿射的,因此他们不会使用异步代码" 和我的应用程序和测试中的错误让我怀疑我们一般不能使用命名的互斥锁来同步UWP应用程序与其后台任务之间的资源访问。我看到的所有替代解决方案都只在进行中。

我得出结论,互斥体在这种情况下确实可以正常工作,只要.ReleaseMutex与.WaitOne在同一级别编码(例如,在.WaitOne之后等待的异步方法中)。

为了方便编码,我封装了互斥锁处理以允许使用using语句:

if (taskemail.getText().toString().trim().length() != 0 &&
     tasktext.getText().toString().trim().length() != 0) {

     Intent i = new Intent(Intent.ACTION_SEND);
     i.setType("message/rfc822");
     i.putExtra(Intent.EXTRA_EMAIL ,taskemail.getText().toString());
     i.putExtra(Intent.EXTRA_SUBJECT, "subject of email");
     i.putExtra(Intent.EXTRA_TEXT   , "body of email");
     try {
         startActivity(Intent.createChooser(i, "Send mail..."));
     } catch (android.content.ActivityNotFoundException ex) {
         Toast.makeText(getActivity(), "There are no email clients installed.", Toast.LENGTH_SHORT).show();
}

或者,可以使用文件锁退出:

'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