I'm building an app that uploading files to Google Drive. WHen I'm binding properties and inoked them, it crash with this error:
System.Runtime.InteropServices.COMException: 'The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))'
my code ->
MyFiles class:
propfull Progress<IUploadProgress> progress;
propfull double Value;
propfull string Name;
propfull double Size;
propfull string fullpath;
//each one of properties has `OnPropertyChanged()` in SET
//More properties...
public virtual void OnPropertyChange([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Debug.WriteLine($"~~~~~~{propertyName} invoked!");
}
public MyFiles(StorageFile file)
{
FileInfo fileInfo = new FileInfo(file.Path);
this.fullpath = file.Path
this.Size = fileInfo.Length;
this.Value = 0;
this.Name = file.name;
this.progress = new Progress<IUploadProgress>();
this.progress += progressChanged; //prog.Report() is calling this
}
private void progressChanged(object sender, IUploadProgress e)
{
if (e.Status == UploadStatus.Uploading)
{
this.Value = (e.BytesSent * 100) / this.Size;
//invoke(this.value) property.
//Binding to ProgressBar.Value
}
}
the MediaPage.xaml
file:
<ListView x:Name="myListView" Grid.Row="1" Grid.Column="1" Height="600" Width="700">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="70">
<StackPanel Orientation="Horizontal">
<Grid>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="10,20" FontSize="20" Grid.Row="0" FontFamily="Yu Gothic UI Semibold"/>
<ProgressBar Value="{Binding Value}" Tag="{Binding Name}" Height="5" Width="200"/>
</Grid>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="upload_btn" Click="ButtonClick" Height="40" Width="150" Content="Upload All Files"/>
the code in MediaPage.xaml.cs
public MediaPage()
{
this.InitializeComponent();
SavedData.files = new ObservableCollection<MyFiles>();
StorageFile file = ...; // testing file
SavedData.files.Add(new MyFiles(file));
myListView.ItemsSource = SavedData.files;
}
private void ButtonClick(object sender, RoutedEventArgs e)
{
for (int i = 0; i < SavedData.files.Count; i++)
{
DriveClass.UploadFile(i, SavedData.files[i].progress); //(index,progress)
}
}
the DriveClass
code:
public void UploadFile(int index, IProgress<IUploadProgress> prog)
{
try
{
StorageFile file = StorageFile.GetFileFromPathAsync([SavedData.files[index].fullpath).AsTask().Result;
var fileMetadata = new Google.Apis.Drive.v3.Data.File()
{
Name = Path.GetFileName(file.Name),
MimeType = "application/octet-stream"
};
using (var stream = file.OpenStreamForReadAsync().Result)
{
var request = this.drive.Files.Create(fileMetadata, stream, "application/octet-stream");
request.ProgressChanged += (IUploadProgress progressInfo) =>
{
if (progressInfo.Status == UploadStatus.Completed)
{
Debug.WriteLine($"done!");
prog.Report(progressInfo);
}
else if (progressInfo.Status == UploadStatus.Failed)
{
Debug.WriteLine($"Failed To Upload Into Google Drive");
prog.Report(progressInfo);
}
else
{
Debug.WriteLine(progressInfo.BytesSent + " has sent");
prog.Report(progressInfo);
}
};
request.ChunkSize = 262144;
request.Upload();
var uploadedFile = request.ResponseBody;
Debug.WriteLine($"File uploaded: {uploadedFile.Name} ({uploadedFile.Id})");
}
}
catch (Exception ex)
{
Debug.WriteLine("Cant uplaod the file because -> " + ex.Message);
}
}
this is the whole cade. everything works fine until I press the button and calling to UploadFile()
.
In UploadFile()
, when the request.progressChanged is active, it called to files[i].progress.ProgressChanged()
.
ProgressChanged()
is updating the files[i].Value
and invoke()
to change the ProgressBar.Value = "{Binding Value}"
the app crash in the Invoke()
line, because I can't update the UI.
After searching, I found something that helped.
the way to Invoke the properties that the UI is connected to, is to add this kind of dispatcher to OnPropertyChanged()
.
public async virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
});
}
After adding this Dispatcher
, the code was running but the app was stuck. so, I turn the UploadFile()
to Task
instead of void
and added the folowing code to ButtonClick()
event in MediaPage.xaml.cs
Task UploadFileTask = Task.Run(() => DriveClass.UploadFile(index,progress));
This also will continue the uploading progress.
I saw the answer in this question.