I'm having Unity go through a large number of iterations using for loops. How can I do this without Unity "freezing up" while the for loop is running?

I’m running slot math simulations in Unity using for loops which iterate through a large number of random screens. I typically run 10 to 100 million simulations in a single run which, depending on the game rules, can take from 1 minute to 30 minutes or more. As I’m waiting for the sims to finish, the Unity project freezes up and becomes unusable, i.e. everything becomes unclickable (scenes, game tab, inspector, etc). How can I make Unity run those sims in the background while still being able to interact with other things within the project? The code running the sims lives in the Update method in a Monobehavior class, and start once a button is clicked. Maybe it can be done differently?

EDIT: The simulations inside the for loop only include mathematical calculations. After reading the example here, I tried the following using Burst:

protected void Simulate(int _runs)
{
    for (int run = 0; run < _runs; run++)
    {
        InitializeMachine();

        float time1 = Time.realtimeSinceStartup;
        stats = new StatsHandler(trials, this);

        SimJob sim = new SimJob { _game = this, _trials = trials, };
        sim.Execute();

        float time2 = Time.realtimeSinceStartup;
        float dt = time2 - time1;
        elapsedTime = string.Format("{0:00}h:{1:00}m:{2:00.000}s", (int)dt / 3600, (int)dt / 60, dt % 60);
        stats.ComputeRTP();

        Debug.Log("game rtp: " + stats.GameRTP);
        Debug.Log(trials + " trials completed in " + elapsedTime);

        SetCustomStats();
        stats.GenerateAndPrintStats(elapsedTime);            
    }
    
}

And this is how I defined my job struct:

[BurstCompile]
public struct SimJob : IJob
{
    public SlotMachine _game;
    public int _trials;

    public void Execute()
    {
        for (int i = 0; i < _trials; i++)
        {
            int win = 0;
            win = _game.Spin();

            if (win > 0) _game.Stats.HitRateCount++;
            if (win > _game.Bet) _game.Stats.WinRateCount++;
            if (win >= _game.Bet * 10) _game.Stats.GoodWinRateCount++;
            _game.Stats.AddToTotalWin(win);
        }
    }
}

… but the computation time is exactly the same as without using Burst…

EDIT: So, before I decide to infuse burst into my ocean of code, I want to make sure I understand how to implement it (and see the value of compiling with burst myself). To do this, I created a class that simulates the probability of getting one side of a fair coin after flipping it with and without using the burst compiler.

using UnityEngine;
using Unity.Jobs;
using Unity.Burst;
using Unity.Collections;

public class Sim : MonoBehaviour
{
    [SerializeField] private int trials = 50000000;
    [SerializeField] private bool usingBurst = false;

    void Start()
    {
  
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            float time1 = Time.realtimeSinceStartup;
            if (usingBurst)
            {
                var trialsArray = new NativeArray<int>(1, Allocator.Persistent);
                trialsArray[0] = trials;
                var successesArray = new NativeArray<int>(1, Allocator.Persistent);
                var rng = new NativeArray<Unity.Mathematics.Random>(1, Allocator.Persistent);
                rng[0] = new Unity.Mathematics.Random((uint)Random.Range(1, 1000000));

                SimJob simJob = new SimJob { _trials = trialsArray, _successes = successesArray, _rng = rng };
                simJob.Run();                
                Debug.Log((double)successesArray[0] / trials);
                trialsArray.Dispose();
                successesArray.Dispose();
                rng.Dispose();
            }
            else
            {                
                int successes = 0;
                for (int i = 0; i < trials; i++)
                {
                    if (Random.Range(0, 2) == 0)
                    {
                        successes++;
                    }
                }
                
                Debug.Log((double)successes / trials);
            }

            float time2 = Time.realtimeSinceStartup;
            Debug.Log("execution time in seconds "+ (usingBurst ? "with " : "without ") + "burst: "  + (time2 - time1));
        }
    }
}

[BurstCompile]
public struct SimJob : IJob
{   
    public NativeArray<int> _trials;
    public NativeArray<int> _successes;
    public NativeArray<Unity.Mathematics.Random> _rng;

    public void Execute()
    {        
        int t = _trials[0];
        int s = 0;
        for (int i = 0; i < t; i++)
        {            
            if (_rng[0].NextInt(0,2) == 0)
            {
                s++;
            }
        }
        _successes[0] = s;       
    }
}

Since I don’t want to use the Random class in my struct, I decided to go with using Mathematics.Random, but it looks like I need to seed it before each coin flip? The code above gives the same value each random trial when executed with burst, is there a way to only have to seed rng[0] once per simulation?

Lookup StartCoroutine. If you make a function return type IEnumerator and use yield return null, it will return control back to Unity so it won’t freeze:

private bool completedTask;

void Start()
{
  StartCoroutine(LongLoopFunction);
}

void Update()
{
  if( completedTask)
  {
    Debug.Log("All Done");
  }
}

private IEnumerator LongLoopFunction()
{
  for(int i = 0; i < 100000; i++)
  {
    expensiveStatement;
    yield return null;
  }
  completedTask = true;
}

The nvoke() method is a delegation mechanism of Unity3D

void Start()

{

    InvokeRepeating("Iktest", 0, 2); 
}

void Update()
{

    if (Input.GetKeyDown(KeyCode.I))
    {
        if (IsInvoking("Iktest"))
        {
            CancelInvoke("Iktest");
            Debug.Log("cancel");
        }
    }
}

void Iktest()
{
    Debug.Log("At work");
}