Hello I'm writing a WPF program that gets has thumbnails inside a ThumbnailViewer. I want to generate the Thumbnails first, then asynchronously generate the images for each thumbnail.
I can't include everything but I think this is whats relevant
Method to generate the thumbnails.
public async void GenerateThumbnails()
{
// In short there is 120 thumbnails I will load.
string path = #"C:\....\...\...png";
int pageCount = 120;
SetThumbnails(path, pageCount);
await Task.Run(() => GetImages(path, pageCount);
}
SetThumbnails(string path, int pageCount)
{
for(int i = 1; i <= pageCount; i ++)
{
// Sets the pageNumber of the current thumbnail
var thumb = new Thumbnail(i.ToString());
// Add the current thumb to my thumbs which is
// binded to the ui
this._viewModel.thumbs.Add(thumb);
}
}
GetImages(string path, int pageCount)
{
for(int i = 1; i <= pageCount; i ++)
{
Dispatcher.Invoke(() =>
{
var uri = new Uri(path);
var bitmap = new BitmapImage(uri);
this._viewModel.Thumbs[i - 1].img.Source = bitmap;
});
}
}
When I run the code above it works just as if I never add async/await/task to the code. Am I missing something? Again What I want is for the ui to stay open and the thumbnail images get populated as the GetImage runs. So I should see them one at a time.
UPDATE:
Thanks to #Peregrine for pointing me in the right direction. I made my UI with custom user controls using the MVVM pattern. In his answer he used it and suggested that I use my viewModel. So what I did is I add a string property to my viewModel and made an async method that loop though all the thumbnails and set my string property to the BitmapImage and databound my UI to that property. So anytime it would asynchronously update the property the UI would also update.
The Task that runs GetImages does virtually nothing but Dispatcher.Invoke, i.e. more or less all your code runs in the UI thread.
Change it so that the BitmapImage is created outside the UI thread, then freeze it to make it cross-thread accessible:
private void GetImages(string path, int pageCount)
{
for (int i = 0; i < pageCount; i++)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(path);
bitmap.EndInit();
bitmap.Freeze();
Dispatcher.Invoke(() => this._viewModel.Thumbs[i].img.Source = bitmap);
}
}
You should also avoid any async void method, excpet when it is an event handler. Change it as shown below, and await it when you call it:
public async Task GenerateThumbnails()
{
...
await Task.Run(() => GetImages(path, pageCount));
}
or just:
public Task GenerateThumbnails()
{
...
return Task.Run(() => GetImages(path, pageCount));
}
An alternative that altogether avoids async/await is a view model with an ImageSource property whose getter is called asynchronously by specifying IsAsync on the Binding:
<Image Source="{Binding Image, IsAsync=True}"/>
with a view model like this:
public class ThumbnailViewModel
{
public ThumbnailViewModel(string path)
{
Path = path;
}
public string Path { get; }
private BitmapImage îmage;
public BitmapImage Image
{
get
{
if (îmage == null)
{
îmage = new BitmapImage();
îmage.BeginInit();
îmage.CacheOption = BitmapCacheOption.OnLoad;
îmage.UriSource = new Uri(Path);
îmage.EndInit();
îmage.Freeze();
}
return îmage;
}
}
}
It looks as though you've been mislead by the constructor of BitmapImage that can take a Url.
If this operation really is slow enough to justify using the async-await pattern, then you would be much better off dividing it into two sections.
a) Fetching the data from the url. This is the slow part - it's IO bound, and would benefit most from async-await.
public static class MyIOAsync
{
public static async Task<byte[]> GetBytesFromUrlAsync(string url)
{
using (var httpClient = new HttpClient())
{
return await httpClient
.GetByteArrayAsync(url)
.ConfigureAwait(false);
}
}
}
b) Creating the bitmap object. This needs to happen on the main UI thread, and as it's relatively quick anyway, there's no gain in using async-await for this part.
Assuming that you're following the MVVM pattern, you shouldn't have any visual elements in the ViewModel layer - instead use a ImageItemVm for each thumbnail required
public class ImageItemVm : ViewModelBase
{
public ThumbnailItemVm(string url)
{
Url = url;
}
public string Url { get; }
private bool _fetchingBytes;
private byte[] _imageBytes;
public byte[] ImageBytes
{
get
{
if (_imageBytes != null || _fetchingBytes)
return _imageBytes;
// refresh ImageBytes once the data fetching task has completed OK
Action<Task<byte[]>> continuation = async task =>
{
_imageBytes = await task;
RaisePropertyChanged(nameof(ImageBytes));
};
// no need for await here as the continuations will handle everything
MyIOAsync.GetBytesFromUrlAsync(Url)
.ContinueWith(continuation,
TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith(_ => _fetchingBytes = false)
.ConfigureAwait(false);
return null;
}
}
}
You can then bind the source property of an Image control to the ImageBytes property of the corresponding ImageItemVm - WPF will automatically handle the conversion from byte array to a bitmap image.
Edit
I misread the original question, but the principle still applies. My code would probably still work if you made a url starting file:// but I doubt it would be the most efficient.
To use a local image file, replace the call to GetBytesFromUrlAsync() with this
public static async Task<byte[]> ReadBytesFromFileAsync(string fileName)
{
using (var file = new FileStream(fileName,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
4096,
useAsync: true))
{
var bytes = new byte[file.Length];
await file.ReadAsync(bytes, 0, (int)file.Length)
.ConfigureAwait(false);
return bytes;
}
}
Rather than involving the the dispatcher and jumping back and forth, I'd do something like this:
private Task<BitmapImage[]> GetImagesAsync(string path, int pageCount)
{
return Task.Run(() =>
{
var images = new BitmapImage[pageCount];
for (int i = 0; i < pageCount; i++)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(path);
bitmap.EndInit();
bitmap.Freeze();
images[i] = bitmap;
}
return images;
}
}
Then, on the UI thread calling code:
var images = await GetImagesAsync(path, pageCount);
for (int i = 0; i < pageCount; i++)
{
this._viewModel.Thumbs[i].img.Source = images[i];
}
Related
So, i have a function that will load an image from disk async in an other thread ( big images will be loaded and I don't want the UI THread to be locked while loading).
Loading is done like this
public override void LoadFile()
{
using (var imageStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Decoder = new TiffBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
InitializeFile();
}
}
Then I want to use the Decoder on the main thread
public List<ThumbnailModel> LoadPages()
{
var result = new List<ThumbnailModel>();
foreach (var frame in Decoder.Frames) <--// this line throws exception
{
result.Add(new ThumbnailModel
{
Name = _metadataLoader.GetPageName((BitmapMetadata)frame.Metadata),
Bitmap = new WriteableBitmap(frame)
});
}
return result;
}
Now here is the problem, whenever I reach the line where I try to access the Decoder.Frames it throws exception (The calling thread cannot access this object because a different thread owns it.)
Is there a way I can use my Decoder in the main thread if not, the only possible solution is to load all the image information in the other thread?
Full code version :
// this is the task, that calls the imageFactory LoadFile method - NewThread
private async Task OpenFileAsync(string strFilePath)
{
var newFile = _imageFileFactory.LoadFile(strFilePath);
if (newFile != null)
{
_imagefile = newFile;
}
}
//image factory load file - NewThread
public IImageFile LoadFile(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
{
return null;
}
var fileExtension = Path.GetExtension(filePath); // .tiff or .jpeg
var file = new ImageFileTiff(filePath, _metatadaFactory, _metadataVersioner);
file.LoadFile();
return file;
}
// ImageFileTiff LoadFile will create a decoder - NewThread
public override void LoadFile()
{
using (var imageStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Decoder = new JpegBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
InitializeFile();
}
}
After we have an IImageFile we call on MainThread(UIThread)
var pages = _imagefile.LoadPages();
Where LoadPages is the place where the app breaks. also called on UIThread
public List LoadPages()
{
var result = new List();
foreach (var frame in Decoder.Frames)
{
result.Add(new ThumbnailModel
{
Name = _metadataLoader.GetPageName((BitmapMetadata)frame.Metadata),
Bitmap = new WriteableBitmap(frame)
});
}
return result;
}
I thought you could simply return the decoder from the thread to be able to access it but your decoder is a TiffBitmapDecoder which inherits from DispatcherObject (https://learn.microsoft.com/en-gb/dotnet/api/system.windows.threading.dispatcherobject?view=netcore-3.1).
So you won't be able to access it from a different thread than the one where it was created msdn:"Only the thread that the Dispatcher was created on may access the DispatcherObject directly"
What you could do instead is use the decoder in it's thread and return the final result:
I couldn't build on your sample since there was to much missing for me to test it but I built a similar project to give an exemple:
public partial class MainWindow : Window
{
public MainWindow()
{
}
public TiffBitmapDecoder LoadFile()
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.InitialDirectory = "c:\\";
openFileDialog.Filter = "tiff files (*.tif)|*.tif|All files (*.*)|*.*";
openFileDialog.FilterIndex = 2;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == true && !string.IsNullOrEmpty(openFileDialog.FileName))
{
//I didn't bother to check the file extension since it's just an exemple
using (var imageStream = openFileDialog.OpenFile())
{
return new TiffBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
}
}
else
{
//User cancelled
return null;
}
}
public List<ThumbnailModel> LoadPages(TiffBitmapDecoder decoder)
{
//TiffBitmapDecoder" inherits from DispatcherObject/>
//https://learn.microsoft.com/en-gb/dotnet/api/system.windows.threading.dispatcherobject?view=netcore-3.1
var result = new List<ThumbnailModel>();
if (decoder != null)
{
try
{
foreach (var frame in decoder.Frames)
{
result.Add(new ThumbnailModel
{
//set the variables
});
}
}
catch(InvalidOperationException e)
{
MessageBox.Show(e.Message, "Error");
}
}
else
{
//Nothing to do
}
return result;
}
private async Task AsyncLoading()
{
this.thumbnailModels = await Task.Run<List<ThumbnailModel>>(() =>
{
var decoder = this.LoadFile();
return this.LoadPages(decoder);
});
}
private List<ThumbnailModel> thumbnailModels = null;
private async void AsyncLoadingButton_Click(object sender, RoutedEventArgs e)
{
await this.AsyncLoading();
}
}
public class ThumbnailModel
{
}
Content of MainWindow.xaml just in case:
<Grid>
<StackPanel Orientation="Vertical">
<Button x:Name="NoReturnButton" Margin="10" HorizontalAlignment="Center" Content="Call AsyncLoadingNoReturn" Click="AsyncLoadingButton_Click" />
</StackPanel>
</Grid>
I wrote a shared library which consumes CefSharp.Offscreen to do the html retrieving work. It works fine when a Console Application calls it. But when a WinForm app connects it, after tcs.TrySetResult(true) is executed, it does not jump into await browser.GetSourceAsync() as what it did in Console App.
In WinForm App, it could be successful if any UI element is not created and not in the UI constructor, but if I create a UI element before calling the shared library, it fails always.
In another way, I force calling "var source = await browser.GetSourceAsync();" to get current html source, but it still does not response in WinForm connection.
[STAThread]
static void Main()
{
// I can put the init code here, but it does not help
//CefSimpleLib.CefTest.Initialize();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
TextBox tb = new TextBox(); // this blocks below call
CefSimpleLib.CefTest cf = new CefSimpleLib.CefTest();
Application.Run(new FormMain());
//CefSimpleLib.CefTest.UnInitialize();
}
namespace CefSimpleLib
{
public class CefTest
{
public CefTest()
{
// You need to replace this with your own call to Cef.Initialize();
// Default is to use an InMemory cache, set CachePath to persist cache
Cef.Initialize(new CefSettings { CachePath = "cache" });
MainAsync();
System.Threading.Thread.Sleep(1000 * 1000);
Cef.Shutdown();
}
private async void MainAsync()
{
var browserSettings = new BrowserSettings();
//Reduce rendering speed to one frame per second, tweak this to whatever suites you best
browserSettings.WindowlessFrameRate = 1;
using (var browser = new ChromiumWebBrowser("https://www.baidu.com", browserSettings))
{
await LoadPageAsync(browser);
var source = await browser.GetSourceAsync();
await Task.Delay(10);
}
}
public Task LoadPageAsync(IWebBrowser browser)
{
var tcs = new TaskCompletionSource<bool>();
EventHandler<LoadingStateChangedEventArgs> handler = null;
handler = (sender, args) =>
{
//Wait for while page to finish loading not just the first frame
if (!args.IsLoading)
{
browser.LoadingStateChanged -= handler;
tcs.TrySetResult(true);
}
};
browser.LoadingStateChanged += handler;
return tcs.Task;
}
}
}
Here's my problem.
I'm loading a few BitmapImages in a BlockingCollection
public void blockingProducer(BitmapImage imgBSource)
{
if (!collection.IsAddingCompleted)
collection.Add(imgBSource);
}
the loading happens in a backgroungwork thread.
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
String filepath; int imgCount = 0;
for (int i = 1; i < 10; i++)
{
imgCount++;
filepath = "Snap";
filepath += imgCount;
filepath += ".bmp";
this.Dispatcher.BeginInvoke(new Action(() =>
{
label1.Content = "Snap" + imgCount + " loaded.";
}), DispatcherPriority.Normal);
BitmapImage imgSource = new BitmapImage();
imgSource.BeginInit();
imgSource.UriSource = new Uri(filepath, UriKind.Relative);
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
blockingProducer(imgSource);
}
}
debugging this part of the code everything looks okay, the problem comes now ...
after finishing loading the images I want to show them in UI one by one. I'm using a dispatcher to do so but I always get the message telling me that the called Thread can not access the object because it belongs to a different Thread.
public void display(BlockingCollection<BitmapImage> results)
{
foreach (BitmapImage item in collection.GetConsumingEnumerable())
{
this.Dispatcher.BeginInvoke(new Action(() =>
{
this.dstSource.Source = item;
Thread.Sleep(250);
}), DispatcherPriority.Background);
}
}
debug accuses that the error is here
this.dstSource.Source = item;
I'm trying everything but cant find out what’s wrong. Anyone has any idea?
You have to call Freeze after loading the images in order to make them accessible to other threads:
BitmapImage imgSource = new BitmapImage();
imgSource.BeginInit();
imgSource.UriSource = new Uri(filepath, UriKind.Relative);
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
imgSource.Freeze(); // here
As far as I have understood the BitmapCacheOption.OnLoad flag, it is only effective when a BitmapImage is loaded from a stream. The Remarks section in BitmapCacheOption says:
Set the CacheOption to BitmapCacheOption.OnLoad if you wish to close a
stream used to create the BitmapImage. The default OnDemand cache
option retains access to the stream until the image is needed, and
cleanup is handled by the garbage collector.
A BitmapImage created from a Uri may be loaded asynchronously (see the IsDownloading property). Consequently, Freeze may not be callable on such a BitmapImage, as downloading may still be in progress after EndInit. I guess it nevertheless works in your case because you are loading BitmapImages from file Uris, which seems to be done immediately.
To avoid this potential problem you may just create the BitmapImage from a FileStream:
var imgSource = new BitmapImage();
using (var stream = new FileStream(filepath, FileMode.Open))
{
imgSource.BeginInit();
imgSource.StreamSource = stream;
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
imgSource.Freeze();
}
For the further future readers, here is the code I used to fix my problem.
public void display(BlockingCollection<BitmapImage> collection)
{
if (collection.IsCompleted || collection.Count != 0)
{
BitmapImage item = collection.Take();
this.Dispatcher.BeginInvoke(new Action(() =>
{
this.dstSource.Source = item;
}), DispatcherPriority.Normal);
}
else
{
dispatcherTimer.Stop();
}
}
public void dispatcherTimer_Tick(object sender, EventArgs e)
{
display(collection);
}
public void configureDispatcherTimer()
{
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
TimeSpan interval = TimeSpan.FromMilliseconds(150);
dispatcherTimer.Interval = interval;
}
My application loads a lot of images in a BackgroundWorker to stay usable. My Image control is bound to a property named "ImageSource". If this is null it's loaded in the background and raised again.
public ImageSource ImageSource
{
get
{
if (imageSource != null)
{
return imageSource;
}
if (!backgroundImageLoadWorker.IsBusy)
{
backgroundImageLoadWorker.DoWork += new DoWorkEventHandler(bw_DoWork);
backgroundImageLoadWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
backgroundImageLoadWorker.RunWorkerAsync();
}
return imageSource;
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
bitmap = new BitmapImage();
bitmap.BeginInit();
try
{
bitmap.CreateOptions = BitmapCreateOptions.DelayCreation;
bitmap.DecodePixelWidth = 300;
MemoryStream memoryStream = new MemoryStream();
byte[] fileContent = File.ReadAllBytes(imagePath);
memoryStream.Write(fileContent, 0, fileContent.Length);
memoryStream.Position = 0;
bitmap.StreamSource = memoryStream;
}
finally
{
bitmap.EndInit();
}
bitmap.Freeze();
e.Result = bitmap;
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BitmapSource bitmap = e.Result as BitmapSource;
if (bitmap != null)
{
Dispatcher.CurrentDispatcher.BeginInvoke(
(ThreadStart)delegate()
{
imageSource = bitmap;
RaisePropertyChanged("ImageSource");
}, DispatcherPriority.Normal);
}
}
This is all well so far but my users can change the images in question. They choose a new image in an OpenDialog, the old image file is overwritten with the new and ImageSource is raised again which loads the new image with the same filename again:
public string ImagePath
{
get { return imagePath; }
set
{
imagePath= value;
imageSource = null;
RaisePropertyChanged("ImageSource");
}
}
On some systems the overwriting of the old file results in an exception:
"a generic error occured in GDI+" and "The process cannot access the file..."
I tried a lot of things like loading with BitmapCreateOptions.IgnoreImageCache and BitmapCacheOption.OnLoad. This raised Exceptions when loading them:
Key cannot be null.
Parameter name: key
If I try this without the BackgroundWorker on the UI thread it works fine. Am I doing something wrong? Isn't it possible to load the images in the background while keeping the files unlocked?
Well, it seems all of the above works. I simplified the example for the question and somehow on the way lost the problem.
The only difference I could see in my code is that the loading of the image itself was delegated to a specific image loader class which somehow created the problem. When I removed this dependency the errors disappeared.
I have problem with converter from Uri to BitmapImage. Uri is url of image on web. I use this converter on item in listbox.
I download image from webpage and create from this stream BitampImage
Problem is if listbox consist about 100 - 250 items, app freeze, I try call WebRequestMethod in another thread but it don’t work.
Here is root part of code:
private static BitmapImage GetImgFromAzet(int sex, Uri imgUri)
{
try
{
if (imgUri == null)
{
if (sex == (int)Sex.Man)
{
return new BitmapImage(new Uri(#"pack://application:,,,/Spirit;Component/images/DefaultAvatars/man.jpg",
UriKind.RelativeOrAbsolute));
}
else
{
return new BitmapImage(new Uri(#"pack://application:,,,/Spirit;Component/images/DefaultAvatars/woman.jpg",
UriKind.RelativeOrAbsolute));
}
}
else
{
BitmapImage image = null;
Task.Factory.StartNew(() =>
{
WebRequest webRequest = WebRequest.CreateDefault(imgUri);
webRequest.ContentType = "image/jpeg";
WebResponse webResponse = webRequest.GetResponse();
image = new BitmapImage();
image.CreateOptions = BitmapCreateOptions.None;
image.CacheOption = BitmapCacheOption.OnLoad;
image.BeginInit();
image.StreamSource = webResponse.GetResponseStream();
image.EndInit();
return image;
//((System.Action)(() =>
//{
// //webResponse.Close();
//})).OnUIThread();
});
return image;
}
}
catch (Exception)
{
//default
return
new BitmapImage(new Uri(PokecUrl.Avatar,UriKind.RelativeOrAbsolute));
}
}
My aim is download image from web, create BitamImage object from him and return as Source of Image control, but I need avoid app freezing. Also problem is if I close webResponse it broke all code.
EDITED:
I try this:
BitmapImage image;
WebRequest req = WebRequest.CreateDefault(imgUri);
req.ContentType = "image/jpeg";
using (var res = req.GetResponse())
{
image = new BitmapImage();
image.CreateOptions = BitmapCreateOptions.None;
image.CacheOption = BitmapCacheOption.OnLoad;
image.BeginInit();
image.UriSource = imgUri;
image.StreamSource = res.GetResponseStream();
image.EndInit();
}
but somewhere must be bug, code is broken.
Any advice?
Binding converter is always executed on UI thread. So you could start other thread in Convert method but eventually (as you need feedback from this thread) you have to wait until it completes, thereby you're blocking your app.
In order to solve this problem, for example, you could use Binding.IsAsync property:
public class ListItemViewData
{
private readonly Uri _uri;
private readonly Sex _sex;
ListItemViewData(Uri uri, Sex sex)
{
this._uri = uri;
this._sex = sex;
}
public BitmapSource Image
{
get
{
// Do synchronous WebRequest
}
}
}
Usage in xaml (inside DataTemplate of listbox item):
<Image Source="{Binding Path=Image, IsAsync=True}"/>
EDITED
I've dived into BitmapImage class and have found out that it has pretty ctor with Uri parameter, that works asynchronously.
So you shouldn't execute WebRequest by yourself. Do just like this:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var uri = (Uri)value;
return new BitmapImage(uri) { CacheOption = BitmapCacheOption.None };
}
EDITED 2
Your view data class.
public class ListItemViewData : INotifyPropertyChanged
{
public ListItemViewData(Uri uri)
{
this._uri = uri;
}
private readonly Uri _uri;
public Uri Uri
{
get
{
return this._uri;
}
}
private BitmapSource _source = null;
public BitmapSource Image
{
get
{
return this._source;
}
set
{
this._source = value;
this.OnPropertyChanged("Image");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string p)
{
var pc = this.PropertyChanged;
if (pc!=null)
{
pc(this, new PropertyChangedEventArgs(p));
}
}
}
Helper, that executes images downloading:
public static class WebHelper
{
public static Stream DownloadImage(Uri uri, string savePath)
{
var request = WebRequest.Create(uri);
var response = request.GetResponse();
using (var stream = response.GetResponseStream())
{
Byte[] buffer = new Byte[response.ContentLength];
int offset = 0, actuallyRead = 0;
do
{
actuallyRead = stream.Read(buffer, offset, buffer.Length - offset);
offset += actuallyRead;
}
while (actuallyRead > 0);
File.WriteAllBytes(savePath, buffer);
return new MemoryStream(buffer);
}
}
}
When you are filling model - you should start separate thread, which will download files and set up images source.
this._listItems.Add(new ListItemViewData(new Uri(#"http://lifeboat.com/images/blue.ocean.jpg")));
//...
var sc = SynchronizationContext.Current;
new Thread(() =>
{
foreach (var item in this._listItems)
{
var path = "c:\\folder\\"+item.Uri.Segments.Last();
var stream = WebHelper.DownloadImage(item.Uri, path);
sc.Send(p =>
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = (Stream)p;
bi.EndInit();
item.Image = bi;
}, stream);
}
}).Start();