As mentioned in the comments, the best practice is to provide async methods to the caller and use async all the way (see this article). However there are 2 things that can already be done:
1. Make your I/O method run asynchronously in a separate thread.
2. Have callers call your method asynchronously even if the implementation is synchronous.
The implementations on client side and on service side are independent. Here is a commented example that I hope shows how to do this. Most of the code below is unnecessary and is there only to illustrate what happens when multiple callers call your method and what is executed when. You may change the Thread.Sleep()
values to simulate different execution time.
I also added a side note regarding the value you return in the Exception, that does not look ok to me.
public class Program
{
public static void Main()
{
// These simulate 3 callers calling your service at different times.
var t1 = Task.Run(() => GetMaximumAvatarId(1));
Thread.Sleep(100);
var t2 = Task.Run(() => GetMaximumAvatarId(2));
Thread.Sleep(2000);
var t3 = Task.Run(() => GetMaximumAvatarId(3));
// Example purposes.
Task.WaitAll(t1, t2, t3);
Console.WriteLine("MAIN: Done.");
Console.ReadKey();
}
// This is a synchronous call on the client side. This could very well be implemented
// as an asynchronous call, even if the service method is synchronous, by using a
// Task and having the caller await for it (GetMaximumAvatarIdAsync).
public static int GetMaximumAvatarId(int callerId)
{
Console.WriteLine($"CALLER {callerId}: Calling...");
var i = GetNumberOfAvatarsInFile(callerId);
Console.WriteLine($"CALLER {callerId}: Done -> there are {i} files.");
return i;
}
// This method has the same signature as yours. It's synchronous in the sense that it
// does not return an awaitable. However it now uses `Task.Run` in order to execute
// `Directory.GetFiles` in a threadpool thread, which allows to run other code in
// parallel (in this example `Sleep` calls, in real life useful code). It finally
// blocks waiting for the result of the task, then returns it to the caller as an int.
// The "callerId" is for the example only, you may remove this everywhere.
public static int GetNumberOfAvatarsInFile(int callerId)
{
Console.WriteLine($" SERVICE: Called by {callerId}...");
var path = GetAvatarsFilePath();
var t = Task.Run(() => Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly).Length);
// Simulate long work for a caller, showing the caller.
Console.WriteLine($" SERVICE: Working for {callerId}...");
Thread.Sleep(500);
Console.WriteLine($" SERVICE: Working for {callerId}...");
Thread.Sleep(500);
Console.WriteLine($" SERVICE: Working for {callerId}...");
Thread.Sleep(500);
Console.WriteLine($" SERVICE: Blocking for {callerId} until task completes.");
return t.Result; // Returns an int.
// --------------------------------------------------------
// Side note: you should return `-1` in the `Exception`.
// Otherwise it is impossible for the caller to know if there was an error or
// if there is 1 avatar in the file.
// --------------------------------------------------------
}
// Unchanged.
private string GetAvatarsFilePath()
{
var webRootPath = webHostEnvironment.WebRootPath;
var path = Path.Combine(webRootPath, "path");
return path;
}
}