简体   繁体   中英

Why Realm throwing Realm accessed from incorrect thread, when the whole operation is executed on the same Thread

Task.Factory.StartNew(async () =>
            {
                try
                {
                    ShowCaseInfo existingShowcase = DBService.GetDB().FetchShowcaseInfo();
                    string previousResponse = existingShowcase?.SerializedResponse;
                    Response response = await CloudService.GetCloudService().FetchShowcase();

                    if (response.Status == ResponseStatus.SUCCESS && !string.Equals(previousResponse, response.Data))
                    {
                        ShowCaseInfo showcaseInfo = JsonConvert.DeserializeObject<ShowCaseInfo>(response.Data, _settings);
                        showcaseInfo.SerializedResponse = response.Data;
                        DBService.GetDB().InsertOrUpdateShowcase(showcaseInfo);
                        FetchShowcaseProducts(showcaseInfo.Showcases);
                    }
                    else
                    {
                        List<Showcase> emptyCases = new List<Showcase>();
                            if (existingShowcase != null)
                            {
                                foreach (Showcase showcase in existingShowcase.Showcases)
                                {
                                    if (showcase.Products.Count == 0)
                                    {
                                        emptyCases.Add(showcase);
                                    }
                                }
                            }
                        FetchShowcaseProducts(emptyCases);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            });

foreach (Showcase showcase in existingShowcase.Showcases) line is throwing the exception. Similarly, in the if condition.string,Equals(previousResponse. response,Data). instead of a local variable had I accessed the previousResponse as existingShowcase,SerializedResponse. some exception was thrown, As per the doc, we are not supposed to pass the object across threads. but in this case all the operations are within the same thread.

in this case all the operations are within the same thread.

No, they're actually not. This is because of how await works.

When await acts asynchronously, it captures its current context - either SynchronizationContext.Current or TaskScheduler.Current . In this case, the context is the thread pool context. So, when the method resumes executing after the await , it may resume executing on any thread pool thread.

As Stephen said in his answer, await will not return to the original calling thread in its continuation unless there is a SychronizationContext on the original thread. When using Task.Run or Task.Factory.StartNew , the worker thread won't have a SychronizationContext by default. The Nito.AsyncEx package (which is actually written by Stephen) provides the AsyncContext class which can be used inside a Task.Run or Task.Factory.StartNew delegate, and any Task s which are await ed inside the delegate passed to AsyncContext.Run will continue on the same thread unless .ConfigureAwait(false) is used.

https://github.com/StephenCleary/AsyncEx/blob/master/doc/AsyncContext.md

To make this easier in the context of Realm, I created a simple extension method to allow asynchronous background-thread work to be done to a Realm database:

public static Task RunTask(this Realm realm, Func<Realm, Task> workFunction) => Task.Run(() => AsyncContext.Run(async () => {
    using var workerRealm = await Realm.GetInstanceAsync(realm.Config);

    await workFunction(workerRealm);
}));

public static Task<TResult> RunTask<TResult>(this Realm realm, Func<Realm, Task<TResult>> workFunction) => Task.Run(() => AsyncContext.Run(async () => {
    using var workerRealm = await Realm.GetInstanceAsync(realm.Config);

    return await workFunction(workerRealm);
}));

In my app I usually have a handle to the UI thread's Realm instance somewhere, so if I ever need to do expensive background work, I just use whateverRealm.RunTask(async backgroundRealm => /* some work with awaits in it */ . The backgroundRealm parameter passed into the delegate will be safe to use anywhere inside the delegate so long as .ConfigureAwait(false) is not called on any tasks being await ed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM