I'm working on a WinRT component in C# which I'm calling from WinJS asyncronously.
I'm calling a library which when called, throws The application called an interface that was marshalled for a different thread
exception. I understand this to be an issue with the thread that the library code is running on, via the UI thread the JS code is running under.
I've found some threads on here which mention ways this can potentially work ( Run code on UI thread in WinRT etc ) but none of these touch on calling code from WinJS, they all tend to be XAML.
So, I'm hoping to answer two questions:
RT Code
public IAsyncOperation<string> ButtonPress()
{
return SaveSpreadsheet().AsAsyncOperation();
}
private async Task<string> SaveSpreadsheet()
{
var res = false;
try
{
CreateSpreadsheet();
AddSomeContentToASheet();
var folder = KnownFolders.DocumentsLibrary;
var outFile = await folder.CreateFileAsync("New.xls", CreationCollisionOption.ReplaceExisting);
res = await book.SaveAsAsync(outFile, ExcelSaveType.SaveAsXLS);
book.Close();
engine.Dispose();
}
catch (Exception e)
{
return e.Message;
}
return "Success! " + res;
}
JS Code
button.onclick = function () {
var rtComponent = new WindowsRuntimeComponent1.Class1();
rtComponent.buttonPress().then(function (res) {
document.getElementById('res').innerText = "Export should have: " + res;
});
};
Dispatcher.RunAsync()
ways of running the RT code, but I come unstuck when I need to get a reference to an IStorageFile
through the WinRT framework methods.Any thoughts greatly appreciated.
I believe what you're missing is a call to Task.Run, which is what actually creates the Task that will run the operation on a separate thread and do the marshaling.
That is, your SaveSpreadsheet function says it's returning Task but doesn't actually create the Task anywhere, and is thus just returning a string. Here's the general structure of an async function in a component:
public IAsyncOperation<string> SomeMethodAsync(int id)
{
var task = Task.Run<string>(async () =>
{
var idString = await GetMyStringAsync(id);
return idString;
});
return task.AsAsyncOperation();
}
So I think you just need to change SaveSpreadsheet to return a String (which your code is doing already, so just change Task to String), and make ButtonPress look like this:
public IAsyncOperation<string> ButtonPress()
{
var task = Task.Run<string>(async () =>
{
return await SaveSpreadsheet();
});
return task.AsAsyncOperation();
}
I cover this subject, by the way, in Chapter 18 of my free ebook from MSPress, Programming Windows Store Apps with HTML, CSS, and JavaScript, 2nd Edition, http://aka.ms/BrockschmidtBook2.
P.S. If there's no reason for your component to be instantiable with new, then you can add the static keyboard to ButtonPress (i.e. public static IAsyncOperation ButtonPress()) and then just make the call with the fully-qualified name WindowsRuntimeComponent1.Class1.buttonPress().then(...). That might simplify your code.