You should be able to set your SelectedIndex first, and without having to include it in your loop:
tbcMain.SelectedIndex = 0;
Then, basing it off of your response, you should be able to either just do .UpdateLayout() on each of your TabItems:
MainTab.UpdateLayout();
tabItem1.UpdateLayout();
tabItem2.UpdateLayout();
tabItem3.UpdateLayout();
tabItem4.UpdateLayout();
tabItem5.UpdateLayout();
Or you should be able to do something like this in your loop:
MainTab.UpdateLayout();
for (int i = 0; i < tbcMain.Items.Count; i++)
{
    TabItem tbi = (TabItem)this.FindControl("tabItem"+i);
    tbi.UpdateLayout();
}
Updating/refreshing should have nothing to do with setting the selected one.  Including the selection of the tab within the loop to i was your problem - not a race condition.  Set the tbcMain.SelectedIndex = 0 outside of your loop and you should be fine.  Sometimes, however, this doesn't work and you need to set it with Dispatcher:
Dispatcher.BeginInvoke((Action)(() => this.tbcMain.SelectedIndex = 0));
There's a write up/comments on a separate thread regarding why it needs to be sent to Dispatcher:
How to programmatically select a TabItem in WPF TabControl
Though, unfortunately for me, I had a similar issue where I was trying to refresh a ListView on a subtab.  Neither .UpdateLayout(), nor .InvalidateVisual() (as I saw on this thread) worked.  I just had to rebind my grid in the button event I was using on my main page, so that when the tab was clicked, it was refreshed manually.  I added an x:Name property on the tab so I could call it using "dot" syntax, and it exposed the ListView.  I simply added the DataTable of results back to that ListView's DataContext.