question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

TrySetSetString can only be called from the main thread.

See original GitHub issue

I am using the Auth Sample in there to Signing an email/pass user:

I receive an error:

TrySetSetString can only be called from the main thread. Constructors and field initializers will be executed from the loading thread when loading a scene. Don’t use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.

` // Create a user with the email and password. public Task CreateUserWithEmailAsync(UserLoginDetails userLogin) { Debug.Log(String.Format(“Attempting to create user {0}…”, email)); // DisableUI();

    // This passes the current displayName through to HandleCreateUserAsync
    // so that it can be passed to UpdateUserProfile().  displayName will be
    // reset by AuthStateChanged() when the new user is created and signed in.
    string newDisplayName = userLogin.name;
    return auth.CreateUserWithEmailAndPasswordAsync(userLogin.email, userLogin.password)
      .ContinueWith((task) =>
      {
          //  EnableUI();
          if (LogTaskCompletion(task, "User Creation"))
          {
              var user = task.Result;
              DisplayDetailedUserInfo(user, 1);
            //  DataController.Instance.LoginProvider = ProviderEnum.password.ToString();
              DataBaseModel.Instance.SaveUserPersonalInfoData(userLogin.country, userLogin.gender, userLogin.email, user.UserId);
              ExecuteNativePopup("Welcome " + displayName, "Email Sign up successfully completed.");
              uiController.OnClick_CloseAllActiveSubMenus();
              return UpdateUserProfileAsync(newDisplayName: newDisplayName);
          }
          return task;
      }).Unwrap();
}
  // Called when a sign-in without fetching profile data completes.
    void HandleSignInWithUser(Task<Firebase.Auth.FirebaseUser> task)
    {
       // EnableUI();
        if (LogTaskCompletion(task, "Sign-in"))
        {
            Debug.Log(String.Format("{0} signed in", task.Result.DisplayName));
            DataController.Instance.LoginProvider = ProviderEnum.password.ToString(); //ERROR IS HERE
            uiController.OnClick_CloseAllActiveSubMenus(); //ALSO CAN HAPPEN HERE
        }
    }
`

Problem is that you cannot execute Unity related outside Unity main thread. What is the correct method to execute Unity tasks after signing ?

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
a-mauricecommented, Dec 13, 2018

Yeah, no worries. So, to just provide you with what we do with our libraries, we have a class that to queue up actions, and provides a method to run on the owning thread. Note that you probably would not normally need something this complex, but for a general solution that handles a lot of cases, this should work.

// Enables callbacks to be dispatched from any thread and be handled on
// the thread that owns the instance to this class (eg. the UIThread).
internal class ThreadDispatcher {
  private int ownerThreadId;
  private System.Collections.Generic.Queue<System.Action> queue =
      new System.Collections.Generic.Queue<System.Action>();

  public ThreadDispatcher() {
    ownerThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
  }

  private class CallbackStorage<TResult> {
    public TResult Result { get; set; }
    public System.Exception Exception { get; set; }
  }

  // Triggers the job to run on the main thread, and waits for it to finish.
  public TResult Run<TResult>(System.Func<TResult> callback) {
    if (ManagesThisThread()) {
      return callback();
    }

    var waitHandle = new System.Threading.EventWaitHandle(
        false, System.Threading.EventResetMode.ManualReset);

    var result = new CallbackStorage<TResult>();
    lock(queue) {
      queue.Enqueue(() => {
        try {
          result.Result = callback();
        }
        catch (System.Exception e) {
          result.Exception = e;
        }
        finally {
          waitHandle.Set();
        }
      });
    }
    waitHandle.WaitOne();
    if (result.Exception != null) throw result.Exception;
    return result.Result;
  }

  // Determines whether this thread is managed by this instance.
  internal bool ManagesThisThread() {
    return System.Threading.Thread.CurrentThread.ManagedThreadId == ownerThreadId;
  }

  // This dispatches jobs queued up for the owning thread.
  // It must be called regularly or the threads waiting for job will be
  // blocked.
  public void PollJobs() {
    System.Diagnostics.Debug.Assert(ManagesThisThread());

    System.Action job;
    while (true) {
      lock(queue) {
        if (queue.Count > 0) {
          job = queue.Dequeue();
        } else {
          break;
        }
      }
      job();
    }
  }
}

In order to use that class, in a MonoBehaviour you would do:

private ThreadDispatcher MyDispatcher;

public Awake() {
  // Create the ThreadDispatcher on a call that is guaranteed to run on the main Unity thread.
  MyDispatcher = new ThreadDispatcher();
}

public Update() {
  MyDispatcher.PollJobs();
}

public TResult RunOnMainThread<TResult>(System.Func<TResult> f) {
  return MyDispatcher.Run(f);
}

And then finally, you could use that with your Auth call like:

return auth.CreateUserWithEmailAndPasswordAsync(userLogin.email, userLogin.password)
      .ContinueWith((task) => {
    return RunOnMainThread(HandleCreateUser(task));
  });

As for await vs ContinueWith, you can think of await as just a different way to format ContinueWith, where instead of the code being within the ContinueWith call, it is instead just after the await. Unfortunately, I believe it would have the same problem that the ContinueWith has, that the await is not guaranteed to return on the main Unity thread.

0reactions
a-mauricecommented, Feb 11, 2019

Hi @robertlair

Sorry for the delay in response. Not entirely sure what that error could mean, unfortunately. My best guess would be that the type of HandleAction isn’t the correct type for it. Basically, you would want to be calling it with something like: RunOnMainThread(() => { return 0; }); In that you give RunOnMainThread a function to run later.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Can only be called from the main thread
Hi, I use Firebase with Unity and I get errors like this ( in Visual Studio -- Unity doesn't throw any errors at...
Read more >
SetActive() can only be called from the main thread
The Issue: Normally, all your code runs on a single thread in Unity, since Unity is single-threaded, however when working with APIs like ......
Read more >
How to fix "get_transform can only be called in main thread ...
Hi I've made a simple code where a 3D object will appear in the scene when it receives an MQTT message from an...
Read more >
Event error: "...can only be called from the main thread"
So I solved this by writing the events to a queue, and then processing the queue synchronously during an update in the main...
Read more >
Unity PlayerPrefs 的一个坑- p1gd0g的个人空间
关键信息是 UnityException: TrySetSetString can only be called from the main thread ,即 SetString 只能在主线程中被调用。 回到代码处:
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found