AWAIT multiple file downloads with DownloadDataAsync - c#

I have a zip file creator that takes in a String[] of Urls, and returns a zip file with all of the files in the String[]
I figured there would be a number of example of this, but I cannot seem to find an answer to "How to download many files asynchronously and return when done"
How do I download {n} files at once, and return the Dictionary only when all downloads are complete?
private static Dictionary<string, byte[]> ReturnedFileData(IEnumerable<string> urlList)
{
var returnList = new Dictionary<string, byte[]>();
using (var client = new WebClient())
{
foreach (var url in urlList)
{
client.DownloadDataCompleted += (sender1, e1) => returnList.Add(GetFileNameFromUrlString(url), e1.Result);
client.DownloadDataAsync(new Uri(url));
}
}
return returnList;
}
private static string GetFileNameFromUrlString(string url)
{
var uri = new Uri(url);
return System.IO.Path.GetFileName(uri.LocalPath);
}

First, you tagged your question with async-await without actually using it. There really is no reason anymore to use the old asynchronous paradigms.
To wait asynchronously for all concurrent async operation to complete you should use Task.WhenAll which means that you need to keep all the tasks in some construct (i.e. dictionary) before actually extracting their results.
At the end, when you have all the results in hand you just create the new result dictionary by parsing the uri into the file name, and extracting the result out of the async tasks.
async Task<Dictionary<string, byte[]>> ReturnFileData(IEnumerable<string> urls)
{
var dictionary = urls.ToDictionary(
url => new Uri(url),
url => new WebClient().DownloadDataTaskAsync(url));
await Task.WhenAll(dictionary.Values);
return dictionary.ToDictionary(
pair => Path.GetFileName(pair.Key.LocalPath),
pair => pair.Value.Result);
}

public string JUST_return_dataURL_by_URL(string URL, int interval, int max_interval)
{
var client = new WebClient(proxy);
client.Headers = _headers;
string downloaded_from_URL = "false"; //default - until downloading
client.DownloadDataCompleted += bytes =>
{
Console.WriteLine("Done!");
string dataURL = Convert.ToBase64String( bytes );
string filename = Guid.NewGuid().ToString().Trim('{', '}')+".png";
downloaded_from_URL =
"Image Downloaded from " + URL
+ "<br>"
+ "<a href=\""+dataURL+"\" download=\""+filename+"\">"
+ "<img src=\"data:image/png;base64," + dataURL + "\"/>"+filename
+ "</a>"
;
return;
};
client.DownloadDataAsync(new System.Uri(URL));
int i = 0;
do{
// Console.WriteLine(
// "(interval > 10): "+(interval > 10)
// +"\n(downloaded_from_URL == \"false\"): " + (downloaded_from_URL == "false")
// +"\ninterval: "+interval
// );
Thread.Sleep(interval);
i+=interval;
}
while( (downloaded_from_URL == "false") && (i < max_interval) );
return downloaded_from_URL;
}

You'd be wanting the task.WaitAll method...
msdn link
Create each download as a separate task, then pass them as a collection.
A shortcut to this might be to wrap your download method in a task.
Return new Task<downloadresult>(()=>{ method body});
Apologies for vagueness, working on iPad sucks for coding.
EDIT:
Another implementation of this that may be worth considering is wrapping the downloads using the parallel framework.
Since your tasks all do the same thing taking a parameter, you could instead use Parallel.Foreach and wrap that into a single task:
public System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, byte[]>> DownloadTask(System.Collections.Generic.IEnumerable<string> urlList)
{
return new System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, byte[]>>(() =>
{
var r = new System.Collections.Concurrent.ConcurrentDictionary<string, byte[]>();
System.Threading.Tasks.Parallel.ForEach<string>(urlList, (url, s, l) =>
{
using (System.Net.WebClient client = new System.Net.WebClient())
{
var bytedata = client.DownloadData(url);
r.TryAdd(url, bytedata);
}
});
var results = new System.Collections.Generic.Dictionary<string, byte[]>();
foreach (var value in r)
{
results.Add(value.Key, value.Value);
}
return results;
});
}
This leverages a concurrent collection to support parallel access within the method before converting back to IDictionary.
This method returns a task so can be called with an await.
Hope this provides a helpful alternative.

Related

Method giving back wrong result during Task

I have a loop creating three tasks:
List<Task> tasks = new List<Task>();
foreach (DSDevice device in validdevices)
{
var task = Task.Run(() =>
{
var conf = PrepareModasConfig(device, alternativconfig));
//CHECK-Point1
string config = ModasDicToConfig(conf);
//CHECK-Point2
if (config != null)
{
//Do Stuff
}
else
{
//Do other Stuff
}
});
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
it calls this method, where some data of a dictionary of a default-config gets overwritten:
private Dictionary<string, Dictionary<string, string>> PrepareModasConfig(DSDevice device, string alternativeconfig)
{
try
{
Dictionary<string, Dictionary<string, string>> config = new Dictionary<string, Dictionary<string, string>>(Project.project.ModasConfig.Config);
if (config.ContainsKey("[Main]"))
{
if (config["[Main]"].ContainsKey("DevName"))
{
config["[Main]"]["DevName"] = device.ID;
}
}
return config;
}
catch
{
return null;
}
}
and after that, it gets converted into a string with this method:
private string ModasDicToConfig(Dictionary<string, Dictionary<string, string>> dic)
{
string s = string.Empty;
try
{
foreach (string key in dic.Keys)
{
s = s + key + "\n";
foreach (string k in dic[key].Keys)
{
s = s + k + "=" + dic[key][k] + "\n";
}
s = s + "\n";
}
return s;
}
catch
{
return null;
}
}
But every Tasks gets the exact same string back.
On //CHECK-Point1 I check the Dic for the changed value: Correct Value for each Task
On //CHECK-Point2 I check the String: Same String on all 3 Tasks (Should be of course different)
Default-Dictionary looks like this: (shortened)
{
{"[Main]",
{"DevName", "Default"},
...
},
...
}
The resulting string look like that:
[Main]
DevName=003 <--This should be different (from Device.ID)
...
[...]
EDIT:
I moved the methods to execute outside the Task. Now I get the correct Results. So I guess it has something to do with the Task?
List<Task> tasks = new List<Task>();
foreach (DSDevice device in validdevices)
{
var conf = PrepareModasConfig(device, alternativconfig));
//CHECK-Point1
string config = ModasDicToConfig(conf);
//CHECK-Point2
var task = Task.Run(() =>
{
if (config != null)
{
//Do Stuff
}
else
{
//Do other Stuff
}
});
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
The problem isn't caused by tasks. The lambda passed to Task.Run captures the loop variable device so when the tasks are executed, all will use the contents of that variable. The same problem would occur even without tasks as this SO question shows. The following code would print 10 times:
List<Action> actions = new List<Action>();
for (int i = 0; i < 10; ++i )
actions.Add(()=>Console.WriteLine(i));
foreach (Action a in actions)
a();
------
10
10
10
10
10
10
10
10
10
10
If the question's code used an Action without Task.Run it would still result in bad results.
One way to fix this is to copy the loop variable into a local variable and use only that in the lambda :
for (int i = 0; i < 10; ++i )
{
var ii=i;
actions.Add(()=>Console.WriteLine(ii));
}
The question's code can be fixed by copying the device loop variable into the loop:
foreach (DSDevice dev in validdevices)
{
var device=dev;
var task = Task.Run(() =>
{
var conf = PrepareModasConfig(device, alternativconfig));
Another way is to use Parallel.ForEach to process all items in parallel, using all available cores, without creating tasks explicitly:
Parallel.ForEach(validdevices,device=>{
var conf = PrepareModasConfig(device, alternativconfig));
string config = ModasDicToConfig(conf);
...
});
Parallel.ForEach allows limiting the number of worker tasks through the MaxDegreeOfParallelism option. It's a blocking call because it uses the current thread to process data along with any worker tasks.

Download multiple files concurrently from FTP using FluentFTP with a maximum value

I would like to download multiple download files recursively from a FTP Directory, to do this I'm using FluentFTP library and my code is this one:
private async Task downloadRecursively(string src, string dest, FtpClient ftp)
{
foreach(var item in ftp.GetListing(src))
{
if (item.Type == FtpFileSystemObjectType.Directory)
{
if (item.Size != 0)
{
System.IO.Directory.CreateDirectory(Path.Combine(dest, item.Name));
downloadRecursively(Path.Combine(src, item.Name), Path.Combine(dest, item.Name), ftp);
}
}
else if (item.Type == FtpFileSystemObjectType.File)
{
await ftp.DownloadFileAsync(Path.Combine(dest, item.Name), Path.Combine(src, item.Name));
}
}
}
I know you need one FtpClient per download you want, but how can I make to use a certain number of connections as maximum, I guess that the idea is to create, connect, download and close per every file I find but just having a X number of downloading files at the same time. Also I'm not sure if I should create Task with async, Threads and my biggest problem, how to implement all of this.
Answer from #Bradley here seems pretty good, but the question does read every file thas has to download from an external file and it doesn't have a maximum concurrent download value so I'm not sure how to apply these both requirements.
Use:
ConcurrentBag class to implement a connection pool;
Parallel class to parallelize the operation;
ParallelOptions.MaxDegreeOfParallelism to limit number of the concurrent threads.
var clients = new ConcurrentBag<FtpClient>();
var opts = new ParallelOptions { MaxDegreeOfParallelism = maxConnections };
Parallel.ForEach(files, opts, file =>
{
file = Path.GetFileName(file);
string thread = $"Thread {Thread.CurrentThread.ManagedThreadId}";
if (!clients.TryTake(out var client))
{
Console.WriteLine($"{thread} Opening connection...");
client = new FtpClient(host, user, pass);
client.Connect();
Console.WriteLine($"{thread} Opened connection {client.GetHashCode()}.");
}
string remotePath = sourcePath + "/" + file;
string localPath = Path.Combine(destPath, file);
string desc =
$"{thread}, Connection {client.GetHashCode()}, " +
$"File {remotePath} => {localPath}";
Console.WriteLine($"{desc} - Starting...");
client.DownloadFile(localPath, remotePath);
Console.WriteLine($"{desc} - Done.");
clients.Add(client);
});
Console.WriteLine($"Closing {clients.Count} connections");
foreach (var client in clients)
{
Console.WriteLine($"Closing connection {client.GetHashCode()}");
client.Dispose();
}
Another approach is to start a fixed number of threads with one connection for each and have them pick files from a queue.
For an example of an implementation, see my article for WinSCP .NET assembly:
Automating transfers in parallel connections over SFTP/FTP protocol
A similar question about SFTP:
Processing SFTP files using C# Parallel.ForEach loop not processing downloads
Here is a TPL Dataflow approach. A BufferBlock<FtpClient> is used as a pool of FtpClient objects. The recursive enumeration takes a parameter of type IEnumerable<string> that holds the segments of one filepath. These segments are combined differently when constructing the local and the remote filepath. As a side effect of invoking the recursive enumeration, the paths of the remote files are sent to an ActionBlock<IEnumerable<string>>. This block handles the parallel downloading of the files. Its Completion property contains eventually all the exceptions that may have occurred during the whole operation.
public static Task FtpDownloadDeep(string ftpHost, string ftpRoot,
string targetDirectory, string username = null, string password = null,
int maximumConnections = 1)
{
// Arguments validation omitted
if (!Directory.Exists(targetDirectory))
throw new DirectoryNotFoundException(targetDirectory);
var fsLocker = new object();
var ftpClientPool = new BufferBlock<FtpClient>();
async Task<TResult> UsingFtpAsync<TResult>(Func<FtpClient, Task<TResult>> action)
{
var client = await ftpClientPool.ReceiveAsync();
try { return await action(client); }
finally { ftpClientPool.Post(client); } // Return to the pool
}
var downloader = new ActionBlock<IEnumerable<string>>(async path =>
{
var remotePath = String.Join("/", path);
var localPath = Path.Combine(path.Prepend(targetDirectory).ToArray());
var localDir = Path.GetDirectoryName(localPath);
lock (fsLocker) Directory.CreateDirectory(localDir);
var status = await UsingFtpAsync(client =>
client.DownloadFileAsync(localPath, remotePath));
if (status == FtpStatus.Failed) throw new InvalidOperationException(
$"Download of '{remotePath}' failed.");
}, new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = maximumConnections,
BoundedCapacity = maximumConnections,
});
async Task Recurse(IEnumerable<string> path)
{
if (downloader.Completion.IsCompleted) return; // The downloader has failed
var listing = await UsingFtpAsync(client =>
client.GetListingAsync(String.Join("/", path)));
foreach (var item in listing)
{
if (item.Type == FtpFileSystemObjectType.Directory)
{
if (item.Size != 0) await Recurse(path.Append(item.Name));
}
else if (item.Type == FtpFileSystemObjectType.File)
{
var accepted = await downloader.SendAsync(path.Append(item.Name));
if (!accepted) break; // The downloader has failed
}
}
}
// Move on to the thread pool, to avoid ConfigureAwait(false) everywhere
return Task.Run(async () =>
{
// Fill the FtpClient pool
for (int i = 0; i < maximumConnections; i++)
{
var client = new FtpClient(ftpHost);
if (username != null && password != null)
client.Credentials = new NetworkCredential(username, password);
ftpClientPool.Post(client);
}
try
{
// Enumerate the files to download
await Recurse(new[] { ftpRoot });
downloader.Complete();
}
catch (Exception ex) { ((IDataflowBlock)downloader).Fault(ex); }
try
{
// Await the downloader to complete
await downloader.Completion;
}
catch (OperationCanceledException)
when (downloader.Completion.IsCanceled) { throw; }
catch { downloader.Completion.Wait(); } // Propagate AggregateException
finally
{
// Clean up
if (ftpClientPool.TryReceiveAll(out var clients))
foreach (var client in clients) client.Dispose();
}
});
}
Usage example:
await FtpDownloadDeep("ftp://ftp.test.com", "", #"C:\FtpTest",
"username", "password", maximumConnections: 10);
Note: The above implementation enumerates the remote directory lazily, following the tempo of the downloading process. If you prefer to enumerate it eagerly, gathering all info available about the remote listings ASAP, just remove the BoundedCapacity = maximumConnections configuration from the ActionBlock that downloads the files. Be aware that doing so could result in high memory consumption, in case the remote directory has a deep hierarchy of subfolders, containing cumulatively a huge number of small files.
I'd split this into three parts.
Recursively build a list of source and destination pairs.
Create the directories required.
Concurrently download the files.
It's the last part that is slow and should be done in parallel.
Here's the code:
private async Task DownloadRecursively(string src, string dest, FtpClient ftp)
{
/* 1 */
IEnumerable<(string source, string destination)> Recurse(string s, string d)
{
foreach (var item in ftp.GetListing(s))
{
if (item.Type == FtpFileSystemObjectType.Directory)
{
if (item.Size != 0)
{
foreach(var pair in Recurse(Path.Combine(s, item.Name), Path.Combine(d, item.Name)))
{
yield return pair;
}
}
}
else if (item.Type == FtpFileSystemObjectType.File)
{
yield return (Path.Combine(s, item.Name), Path.Combine(d, item.Name));
}
}
}
var pairs = Recurse(src, dest).ToArray();
/* 2 */
foreach (var d in pairs.Select(x => x.destination).Distinct())
{
System.IO.Directory.CreateDirectory(d);
}
/* 3 */
var downloads =
pairs
.AsParallel()
.Select(x => ftp.DownloadFileAsync(x.source, x.destination))
.ToArray();
await Task.WhenAll(downloads);
}
It should be clean, neat, and easy to reason about code.

What's the best way to make method run async?

Hi i'm new in async programming. How can I run my method checkAvaible to run async? I would like to download 2500 pages at once if it's possible, dont wait to complete one download and start another. How can I make it?
private static void searchForLinks()
{
string url = "http://www.xxxx.pl/xxxx/?action=xxxx&id=";
for (int i = 0; i < 2500; i++)
{
string tmp = url;
tmp += Convert.ToString(i);
checkAvaible(tmp); // this method run async, do not wait while one page is downloading
}
Console.WriteLine(listOfUrls.Count());
Console.ReadLine();
}
private static async void checkAvaible(string url)
{
using (WebClient client = new WebClient())
{
string htmlCode = client.DownloadString(url);
if (htmlCode.IndexOf("Brak takiego obiektu w naszej bazie!") == -1)
listOfUrls.Add(url);
}
}
You would not want to download 2500 pages at the same time since this will be a problem for both your client and the server. Instead, I have added a concurrent download limitation (of 10 by default). The web pages will be downloaded 10 page at a time. (Or you can change it to 2500 if you are running a super computer :))
Generic Lists (I think it is a List of strings in your case) is not thread safe by default therefore you should synchronize access to the Add method. I have also added that.
Here is the updated source code to download pages asynhcronously with a configurable amount of concurrent calls
private static List<string> listOfUrls = new List<string>();
private static void searchForLinks()
{
string url = "http://www.xxxx.pl/xxxx/?action=xxxx&id=";
int numberOfConcurrentDownloads = 10;
for (int i = 0; i < 2500; i += numberOfConcurrentDownloads)
{
List<Task> allDownloads = new List<Task>();
for (int j = i; j < i + numberOfConcurrentDownloads; j++)
{
string tmp = url;
tmp += Convert.ToString(i);
allDownloads.Add(checkAvaible(tmp));
}
Task.WaitAll(allDownloads.ToArray());
}
Console.WriteLine(listOfUrls.Count());
Console.ReadLine();
}
private static async Task checkAvaible(string url)
{
using (WebClient client = new WebClient())
{
string htmlCode = await client.DownloadStringTaskAsync(new Uri(url));
if (htmlCode.IndexOf("Brak takiego obiektu w naszej bazie!") == -1)
{
lock (listOfUrls)
{
listOfUrls.Add(url);
}
}
}
}
It's best to convert code to async by working from the inside and proceeding out. Follow best practices along the way, such as avoiding async void, using the Async suffix, and returning results instead of modifying shared variables:
private static async Task<string> checkAvaibleAsync(string url)
{
using (var client = new HttpClient())
{
string htmlCode = await client.GetStringAsync(url);
if (htmlCode.IndexOf("Brak takiego obiektu w naszej bazie!") == -1)
return url;
else
return null;
}
}
You can then start off any number of these concurrently using Task.WhenAll:
private static async Task<string[]> searchForLinksAsync()
{
string url = "http://www.xxxx.pl/xxxx/?action=xxxx&id=";
var tasks = Enumerable.Range(0, 2500).Select(i => checkAvailableAsync(url + i));
var results = await Task.WhenAll(tasks);
var listOfUrls = results.Where(x => x != null).ToArray();
Console.WriteLine(listOfUrls.Length);
Console.ReadLine();
}

Calculate loading time of multiple websites using AsyncTask in Xamarin Android and C#.Net

I have a string array which contains addresses of websites:
string[] arr = new string[]
{
"https://www/google.com",
"https://www.yahoo.com",
"https://www.microsoft.com"
};
I have to send these URLs as argument to the asynctask method so that I will be able to calculate the loading time of each website. I don't have to show the website pages, so I am not using webview.
I can use stopwatch or httprequest to calculate the loading time and my ultimate goal is that all the websites need to start loading at the same time asynchronously, and output has to look like the following
Loading time
google - 00:00:04:092345 (hr:min:sec:millisec) yahoo - 00:00:06:028458
How can I send an array to asynctask and how I can generate loading time without using await?
Here is a brief solution of what you could do.
This is not complete nor perfect. It will will give you the loading time of one URL. Also there is a suggestion of how you could extend this to multiple URLs.
You will need a WebView, either in code or from UI.
Load the URL into the WebView using webview.LoadUrl("https://www/google.com");.
Create a new class by extending it from WebViewClient as follows:
public class myWebViewClient : WebViewClient
{
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
Console.WriteLine("OnPageFinished for url : " + url + " at : " + DateTime.Now);
}
}
In your OnCreate() method add the following line of code :
webview.SetWebViewClient(new myWebViewClient());
So from here what you have to do is, Create a Dictionary with URL as key and Loading time as value. Set all the loading time to 0 initially. Update the value corresponding to each URL in the OnPageFinished(). Create an async Task function which would return you the populated dictionary.
public async Task<Dictionary<string, double>> myAsyncFunction()
{
await Task.Delay(5); //to make it async
//Wait till all the OnPageFinished events have fired.
while (myDictionary.Any(x=>x.Value == 0) == true)
{
//there are still websites which have not fully loaded.
await Task.Delay(1); //wait a millisecond before checking again
}
return myDictionary;
}
You can call myAsyncFunction() in a seprate thread than your UI and implement the ContinueWith() or just let it run in a separate thread and write that output into somewhere that you can check when required.
eg : Task.Run(async () => await myAsyncFunction());
UPDATE : based on OP's comments
In the UI thread :
var myClassList = new List<myClass>
{
new myClass{URL = "https://www/google.com", TimeTaken = null},
new myClass{URL = "https://www.yahoo.com", TimeTaken = null},
new myClass{URL = "https://www.microsoft.com", TimeTaken = null}
};
Console.WriteLine("Started at : " + DateTime.Now.ToShortTimeString());
var business = new BusinessLogic();
var loadtimetask = business.GetLoadTimeTakenAsync(myClassList);
await loadtimetask;
Console.WriteLine("Completed at : " + DateTime.Now.ToShortTimeString());
And implementation class :
public async Task<List<myClass>> GetLoadTimeTakenAsync(List<myClass> myClassList)
{
Parallel.ForEach(myClassList, myClassObj =>
{
using (var client = new HttpClient())
{
myClassObj.StartTime = DateTime.Now;
var stream = client.GetStreamAsync(myClassObj.URL)
.ContinueWith((s) =>
{
if (s.IsCompleted)
{
var myClassObjCompleted = myClassList.Where(x => x.URL == myClassObj.URL).First();
myClassObjCompleted.EndTime = DateTime.Now;
myClassObjCompleted.TimeTaken = myClassObj.EndTime - myClassObj.StartTime;
}
});
Task.Run(async () => await stream);
}
});
while (myClassList.Any(x => x.TimeTaken == null))
{
await Task.Delay(1);
}
return myClassList;
}
//Create TextView to display status of Wifi
TextView wifitext = FindViewById<TextView>(Resource.Id.WifiTextView);
//Configuring Wifi connection
var connectivityManager = (ConnectivityManager)GetSystemService(ConnectivityService);
var activeConnection = connectivityManager.ActiveNetworkInfo;
if (activeConnection != null && activeConnection.IsConnected)
{
wifitext.Text = "WIFI AVAILABLE";
string[] urladdress = new string[] { "https://www.google.com/", "https://www.yahoo.com/"};
for (int i = 0; i < urladdress.Length; i++)
{
string url = urladdress[i];
//Call async method
Task returnedTask = Task_MethodAsync(url);
}
}
else
wifitext.Text = "WIFI UNAVAILABLE";
}
public async Task Task_MethodAsync(string url)
{
LinearLayout ll = FindViewById<LinearLayout>(Resource.Id.linearLayout1);
WebClient client = new WebClient();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Stream listurl = client.OpenRead(url);
StreamReader reader = new StreamReader(listurl);
stopwatch.Stop();
// listurl.Close();
var time = Convert.ToString(stopwatch.Elapsed);

Returning Void in Async method from WEB API Controller

I have this async method inside ASP.NET MVC 4 WEB API Controller that I got from this blog:
http://www.strathweb.com/2012/04/html5-drag-and-drop-asynchronous-multi-file-upload-with-asp-net-webapi/
public async Task<IList<RecivedFile>> Post()
{
List<RecivedFile> result = new List<RecivedFile>();
if (Request.Content.IsMimeMultipartContent())
{
try
{
MultipartFormDataStreamProvider stream = new MultipartFormDataStreamProvider(HostingEnvironment.MapPath("~/USER-UPLOADS"));
IEnumerable<HttpContent> bodyparts = await Request.Content.ReadAsMultipartAsync(stream);
IDictionary<string, string> bodyPartFiles = stream.BodyPartFileNames;
IList<string> newFiles = new List<string>();
foreach (var item in bodyPartFiles)
{
var newName = string.Empty;
var file = new FileInfo(item.Value);
if (item.Key.Contains("\""))
newName = Path.Combine(file.Directory.ToString(), item.Key.Substring(1, item.Key.Length - 2));
else
newName = Path.Combine(file.Directory.ToString(), item.Key);
File.Move(file.FullName, newName);
newFiles.Add(newName);
}
var uploadedFiles = newFiles.Select(i =>
{
var fi = new FileInfo(i);
return new RecivedFile(fi.Name, fi.FullName, fi.Length);
}).ToList();
result.AddRange(uploadedFiles);
}
catch (Exception e)
{
}
}
return result;
}
My question is why exactly does this method have a return type of Task? It is not clear "where to" or "to whom" it returns this task? It's like there is no one that waits for/receives the returned object.
I wonder what will be the implications if I return void like this:
EDIT:
I have tried the code below and it completely breaks the program. it's like the runtime looses the reference to the code, the code itself doesn't finish running.
public async void Post()
{
List<RecivedFile> result = new List<RecivedFile>();
if (Request.Content.IsMimeMultipartContent())
{
try
{
MultipartFormDataStreamProvider stream = new MultipartFormDataStreamProvider(HostingEnvironment.MapPath("~/USER-UPLOADS"));
IEnumerable<HttpContent> bodyparts = await Request.Content.ReadAsMultipartAsync(stream);
IDictionary<string, string> bodyPartFiles = stream.BodyPartFileNames;
IList<string> newFiles = new List<string>();
foreach (var item in bodyPartFiles)
{
var newName = string.Empty;
var file = new FileInfo(item.Value);
if (item.Key.Contains("\""))
newName = Path.Combine(file.Directory.ToString(), item.Key.Substring(1, item.Key.Length - 2));
else
newName = Path.Combine(file.Directory.ToString(), item.Key);
File.Move(file.FullName, newName);
newFiles.Add(newName);
}
}
catch (Exception e)
{
}
}
}
The ASP.NET runtime waits for it. You may find this video useful.
Most examples for async assume a UI context. ASP.NET also provides a context, but it's a "request" context - one for each HTTP request. My async/await post gives an overview of this "context", and the async/await FAQ goes into much more detail.
With return type void you don't wait for an object to return.. You wait for an operation to finish. You wait for your Task to finish.
If you return void, you will be returning 204 "No Content" Response message immediately regardless of the completion status of your asynchronous operation. This is done by the help of VoidResultConverter.
Note: On RC, you will see that it returns 200 "OK" response but with
the RTM, it will return 204 "No Content" response.

Categories

Resources