问题
I'm not sure if the Title is a good description of this issue or not. Essentially what I have is a WinForm app that retrieves a list of files from a folder into a ListView, then a button is clicked to upload them via FTP to a remote server.
Functionally speaking, the app works as expected:
- Open app
- Review list of files in ListView control
- Click Upload button
- Files listed in ListView are uploaded; after each successful upload the ListView is updated to show 'Success'
- After all files are uploaded, operation stops.
My issue is, after clicking the upload button the UI is pretty much unresponsive until the operation finishes. The ListView updates as expected as each file is uploaded and even keeps the active row in focus. Here is the for loop that processes the files. A little background - in the code excerpt below, each for...loop processes 2 files - the primary file is the only one that shows in the ListView. The 2nd file in each loop is a trigger file that is sent after its primary is sent, ie: .primary, .trigger. Both files have to send in order to register a success. If a primary file does not have a corresponding trigger file, it won't be available in the ListView for upload.
foreach (ListViewItem item in lvSourceFiles.Items)
{
int rowIndex = item.Index;
string fileName = item.SubItems[2].Text;
lvSourceFiles.EnsureVisible(rowIndex);
transferStatus = "Failed"; // Set this as a default
// Transfer the source file first
transferResult = session.PutFiles(readyFile, destFile, false, transferOptions);
// Throw on any error
transferResult.Check();
// If the source file transfer was successful, then transfer the trigger file
if (transferResult.IsSuccess)
{
transferResult = session.PutFiles(triggerFile, destFile, false, transferOptions);
transferResult.Check();
if (transferResult.IsSuccess)
{
transferStatus = "Success";
}
}
UpdateResultsToListView(lvSourceFiles, rowIndex, fileName, transferStatus);
}
Is this a situation where I need to implement some sort of asynchronous functionality, or is there a better way to do this so the UI doesn't freeze during the upload process? Essentially I want to be able to interact with the form while the upload is running, such as having a cancel button to stop the upload. As it stands, I can't do anything with the form until the job completes, or I terminate the app.
Thanks, James
回答1:
You could offload the long-running operation to a ThreadPool
thread, by using async/await and the handy Task.Run method:
transferResult = await Task.Run(() => session.PutFiles(readyFile, destFile, false, transferOptions));
...and:
transferResult = await Task.Run(() => session.PutFiles(triggerFile, destFile, false, transferOptions));
You should also add the async modifier in the event handler, in order to enable the await operator.
Important: Avoid doing anything UI related in the offloaded method. If you want to communicate with the UI during the operation, for example for progress reporting, use the Progress<T> class.
回答2:
You cannot do lengthy operations on the GUI thread. Do them on a background thread.
The answer by @Theodor correctly shows that you can move the PutFiles
to the thread pool.
Another option is to move all your upload logic to the thread pool and call back to the main thread using Control.Invoke for GUI updates only.
For a full example, see WinSCP article Displaying FTP/SFTP transfer progress on WinForms ProgressBar.
Pick the option that suits you better. I believe that my approach is easier to grasp for someone unexperienced with thread programming.
来源:https://stackoverflow.com/questions/64003533/ui-unresponsive-until-action-is-complete