You can't put that loop in its own thread. Anything that involves the UI must be done on the UI thread. That said, it's only the part of it that directly involves the UI that needs to be done on the UI thread. The rest can be done on a secondary thread. So, what you need to do is break it up into three parts:
1. Get the data you require from the UI. In this case, that would mean looping through the items in the ListView and getting the Tag of each one and putting it somewhere that you can use later. The obvious choice would be a String array.
2. Do all the work that doesn't directly involve the UI using the data from step 1. In this case, that would be loading the Images.
3. Update the UI with the data from step 2. In this case, that would be associating each ListViewItem with the appropriate Image.
Get that working in a single thread first, i.e. replace your existing loop with three separate sections of code that implement those three steps. Once you've got that working, you can simply break the second section out into a separate thread.
With regards to step 2, you may or may not be able to actually add the Images to the ImageList on the secondary thread. If the ImageList is not associated with the ListView then you definitely can. If the ImageList is associated with the ListView but you don't actually try to associated an Image with a ListViewItem on the secondary thread then I'm not too sure either way, so you'd have to test that.
What I would suggest is that you actually put those three sections of code into three separate methods. You can then just call each of those three methods where your current loop is. Once it's working, the multi-threading part is very easy:
1. Add a BackgroundWorker and handle its DoWork and RunWorkerCompleted events.
2. After your call to the first of your three methods, call the BGW's RunWorkerAsync method and pass the data you retrieved in that first method.
3. Move the call to your second method into the DoWork event handler. Get the input from the e.Argument property and assign the output to the e.Result property.
4. Move the call to your third method into the RunWorkerCompleted event handler. Get the output from the e.Result property.
Here's some useful information on how to use a BackgroundWorker and pass data between threads:
Using the BackgroundWorker Component
When it comes time to actually update your ListView, make sure you call its BeginUpdate method first and its EndUpdate method when you're done.
Now, there's quite a bit of information there but don't let it overwhelm you. Each step is fairly simple so take it one step at a time and don't move on to the next until the previous is done and working. Certainly implementing the first three steps is something you can do because you've already done it. You just need to expand out your existing code. Start with the step 1. When it's done, move on to step 2, etc. I'm more than willing to help with issues along the way but I won't be providing any more information on any stage there if you haven't already completed the stages before it. That would be counter-productive.