Quickstart-unity: Please run function in RunTransaction on the unity main thread

Created on 23 Sep 2018  路  14Comments  路  Source: firebase/quickstart-unity

When we call RunTransaction it cannot use GameObject.GetComponent and other function that need to call in unity main thread. Please run function callback in RunTransaction in the unity main thread

bug

Most helpful comment

@stewartmiles And when will you expose that FirebaseHandler.RunOnMainThread ?

All 14 comments

I would to prefer to have it run asynchronously in whatever thread detects the completion.
That way we can decide when we need to post to the main thread and how we want to synchronize.

I.e. we would use:
.. SyncContext.RunOnUnityThread( ()=> gameObject.AddComponent<Type>() ); ..
If you need a sample implementation of RunOnUnityThread just let me know.

@dwulive At least RunTransactionOnMainThread should be there to have cleaner code. I have made it myself but I think it should be provided from SDK because most of the times we use unity we need to access the code that not normally thread safe

As @dwulive points out, it's probably not great to force everything to run on the main thread when it doesn't have to. If you're modifying data structures that don't touch Unity APIs there is no need to run the code on the main thread.

Internally, we have functionality that allows us to run code on the main thread. I guess we could expose this functionality which would allow something like this...

reference.RunTransaction(AddScoreTransaction)
        .ContinueWith(task => {
          if (task.Exception != null) {
            DebugLog(task.Exception.ToString());
          } else if (task.IsCompleted) {
            FirebaseHandler.RunOnMainThread(() => {
              DebugLog("Transaction complete.");
              return true;
            });
          }
        });

@stewartmiles The problem is RunTransaction itself take in the function callback that would access the data in unity system

C# reference.RunTransaction((data) => { data.SetValue("value",gameObject.GetComponent<SomeComponent>().value); // error here return TransactionResult.Success(data); });

It turn out that RunTransaction itself don't have overload method to let us return Task<TransactionResult>. And RunTransaction was call that callback in a thread that not unity main thread so the code above will throw error

If callback of RunTransaction could accept Task<TransactionResult> I could use the sync context to get the value I need and set into that mutableData in RunTransaction callback directly. So it need to fix someway or another

And for simplicity I think the SDK should just call RunTransaction callback in unity own thread. Or having additional API RunTransactionOnMainThread

Aside from that, after the end of RunTransaction I would return back to my own code and I could use synccontext myself

But FirebaseHandler.RunOnMainThread seem like an API that should be expose so I don't need to have my own version

@stewartmiles And when will you expose that FirebaseHandler.RunOnMainThread ?

Hi all,

As of 6.0.0, the Firebase Unity SDK now has extension methods on Tasks to allow calling functions on the main Unity thread when the Task has finished. For more informantion, see: https://firebase.google.com/docs/reference/unity/class/firebase/extensions/task-extension.html

@a-maurice I don't think this solve the main issue of this thread, an API for RunTransaction that will allow the internal code to run on main thread

Ah, you are correct, that this is about getting RunTransaction on the main thread, not about Tasks continuing on it. Reopening this issue, so that we can look into it more.

Hi @Thaina

Here comes the 3rd person supporting your issue! :D

Given your sample code, I think it is better to do it this way.

var dataToSet = gameObject.GetComponent<SomeComponent>().value.Clone();
reference.RunTransaction((data) => {
    data.SetValue("value", dataToSet);
    return TransactionResult.Success(data);
});

Here is why:

  • The transaction function can be executed multiple times if multiple clients are writing to the same location at the same time. That is, if gameObject.GetComponent<SomeComponent>().value can be changed after you call RunTransaction(), the value you access during the transaction Func can be different from the one you were planning to write to the database. Then this is a race condition. It is better to clone the data you want to set to the server first.
  • FirebaseHandler.RunOnMainThread is not that great in this scenario since it blocks the current thread and wait for the value returned from the main thread. And if you are expecting to compete with other clients to set value at this location, you probably do not want to do block call in your transaction function.
  • Well, competing to set a value in the database based on a mutable value on the game object living on each client just sounds very racy to me. :/

Since RunTransaction() relies on multiple roundtrip between client and server to complete, it does not make to much sense to have it accessing mutable data in GameObject.

Hope this answer your question!

Shawn

@Thaina According to the code we have, it should be called from the main thread anyway...
I'll open an investigation to see why it is not.

@chkuang-g Maybe it was already called from the main thread since some version. This request was made so long time ago and I didn't have test it again for any newer version since I open this issue

The transaction function can be executed multiple times if multiple clients are writing to the same location at the same time. That is, if gameObject.GetComponent().value can be changed after you call RunTransaction(), the value you access during the transaction Func can be different from the one you were planning to write to the database

I request this because it is opposite. I purposely get the component inside RunTransaction because I want to acquire the freshest value in the system to write into database. If I want to write exact value I know I could just cache it before RunTransaction as you describe. But there was no way we could purposely get the most updated component unless the RunTransaction would run on main thread or accept async so we could access main thread in it

And the use case it not really about multiple client fight for 1 record. It just to make a transaction. It maybe a save game of 1 person but the point is to have multiple record will be written in ACID way, 1 record must increase and another record must also decreased, all or none, something like that

Is the 6.2.0 was fixing this?

Yes, this should be fixed as of the latest release, 6.2.0

Was this page helpful?
0 / 5 - 0 ratings