我正在尝试实现一个简单的 Windows 8 C# XAML 应用程序,其中有两个调用来访问单个 Web 服务,一个来自项目以加载和显示数据,另一个用于显示通知。由于对同一个 Web 服务进行了两次调用,我希望如果已经对该服务进行了一次调用,则另一个调用应该等待并使用第一次调用的相同响应。
我怎样才能实现这种功能?我没有添加任何代码,因为我没有为此编写任何代码。我只是想先思考,然后再编码。
请让我知道我可以为这种项目结构获得一些帮助吗?
我正在尝试实现一个简单的 Windows 8 C# XAML 应用程序,其中有两个调用来访问单个 Web 服务,一个来自项目以加载和显示数据,另一个用于显示通知。由于对同一个 Web 服务进行了两次调用,我希望如果已经对该服务进行了一次调用,则另一个调用应该等待并使用第一次调用的相同响应。
我怎样才能实现这种功能?我没有添加任何代码,因为我没有为此编写任何代码。我只是想先思考,然后再编码。
请让我知道我可以为这种项目结构获得一些帮助吗?
您可以通过缓存Task
当前正在下载的内容来做到这一点,如果有缓存,则不再开始下载Task
:
private volatile Task<string> m_cachedWebServiceTask;
async Task<string> AccessWebServiceAsync()
{
var task = m_cachedWebServiceTask;
if (task == null)
task = m_cachedWebServiceTask = DownloadFromWebServiceAsync();
try
{
return await task;
}
finally
{
m_cachedWebServiceTask = null;
}
}
请注意,此代码有一个竞争条件:如果您AccessWebServiceAsync()
同时调用两次,也有很小的机会DownloadFromWebServiceAsync()
会被调用两次。但由于这只是一个优化,我认为这不应该是一个问题。如果这对您来说是个问题,您需要用锁来保护进入该领域的通道。
由于我觉得这个问题需要进一步关注并且它的解决方案仍然可以优化,我决定发布另一种方法。OP 主要是关于利用以下 3 个需求范围的问题:应用程序内的用户体验、应用程序的内部需求和Web 服务的加载多个请求。
因此,管理在这很短的时间内发生的事情实际上是解决问题的方法。
在客户端,Service1Client
类:
public partial class Service1Client
{
// default time buffer value
int _timeBuffer = 100;
// a time buffer used for returning the same response
public int TimeBuffer
{
get { return _timeBuffer; }
set { _timeBuffer = value; }
}
// the start and end time of the web service request
static DateTime _start, _end;
// a volatile static variable to store the response in the buffering time
volatile static string _response;
// used for blocking other threads until the current thread finishes it's job
object _locker = new object();
public async Task<string> GetResponseData()
{
return await Task.Factory.StartNew<string>(() =>
{
lock (_locker)
{
if (DateTime.Now >= _end.AddMilliseconds(TimeBuffer))
{
_start = DateTime.Now;
var async = GetDataAsync();
_response = async.Result;
_end = DateTime.Now;
}
}
return _response;
});
}
}
用于测试的控制台应用程序:
class Program
{
static void Main(string[] args)
{
while (true)
{
var client = new ServiceReference1.Service1Client();
client.TimeBuffer = 150;
Console.WriteLine(client.GetResponseData().Result);
if (Console.ReadKey().Key == ConsoleKey.Enter)
break;
}
}
}
作为备注,请注意,出于清晰示例的原因,我决定将GetDate
WCF 服务方法的返回类型从DateTime
更改为string
。
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData();
}
public class Service1 : IService1
{
public string GetData()
{
System.Threading.Thread.Sleep(5000);
return DateTime.Now.ToString();
}
}
对于您的方案,一个可行的想法是扩展服务类。
IService1
接口定义:
[ServiceContract]
public interface IService1
{
[OperationContract]
DateTime GetData();
}
类Service1
定义:
public class Service1 : IService1
{
public DateTime GetData()
{
System.Threading.Thread.Sleep(5000);
return DateTime.Now;
}
}
在客户端,扩展Service1Client
类定义并添加一个新方法:
public partial class Service1Client
{
static bool _isOpen;
static DateTime? _cachedResponse;
object _locker = new object();
public DateTime GetResponseData()
{
if (!_isOpen)
{
if (!_cachedResponse.HasValue)
{
lock (_locker)
{
_isOpen = true;
_cachedResponse = GetData();
_isOpen = false;
}
return _cachedResponse.Value;
}
else
{
Task.Factory.StartNew<DateTime>(() =>
{
lock (_locker)
{
_isOpen = true;
_cachedResponse = GetData();
_isOpen = false;
}
return _cachedResponse.Value;
});
}
}
return _cachedResponse.Value;
}
}
测试它:
class Program
{
static void Main(string[] args)
{
while (true)
{
var client = new ServiceReference1.Service1Client();
Console.WriteLine(client.GetResponseData());
if (Console.ReadKey().Key == ConsoleKey.Enter)
break;
}
}
}